Skip to content

Commit 31d5ce4

Browse files
committed
feat: Edit returning resolutions for vulnerabilities
If the vulnerability resolution has been defined in the server, add the definition for the resolution in the vulnerabilities for run query. Relates to #1009. Signed-off-by: Johanna Lamppu <[email protected]>
1 parent 70aff36 commit 31d5ce4

File tree

9 files changed

+295
-8
lines changed

9 files changed

+295
-8
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import org.eclipse.apoapsis.ortserver.model.AdvisorJob
9292
import org.eclipse.apoapsis.ortserver.model.AdvisorJobConfiguration
9393
import org.eclipse.apoapsis.ortserver.model.AnalyzerJob
9494
import org.eclipse.apoapsis.ortserver.model.AnalyzerJobConfiguration
95+
import org.eclipse.apoapsis.ortserver.model.AppliedVulnerabilityResolution
9596
import org.eclipse.apoapsis.ortserver.model.ContentManagementSection
9697
import org.eclipse.apoapsis.ortserver.model.EcosystemStats
9798
import org.eclipse.apoapsis.ortserver.model.EnvironmentConfig
@@ -597,6 +598,14 @@ fun AdvisorDetails.mapToApi() = ApiAdvisorDetails(
597598
capabilities = capabilities.map { ApiAdvisorCapability.valueOf(it.name) }.toSet()
598599
)
599600

601+
fun AppliedVulnerabilityResolution.mapToApi() =
602+
ApiVulnerabilityResolution(
603+
externalId = resolution.externalId,
604+
reason = resolution.reason,
605+
comment = resolution.comment,
606+
definition = definition?.mapToApi()
607+
)
608+
600609
fun VulnerabilityWithDetails.mapToApi() =
601610
ApiVulnerabilityWithDetails(
602611
vulnerability = vulnerability.mapToApi(),

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

Lines changed: 6 additions & 1 deletion
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
* Defines the resolution of a Vulnerability. This can be used to silence false positives, or vulnerabilities that
2628
* have been identified as not being relevant.
@@ -29,5 +31,8 @@ import kotlinx.serialization.Serializable
2931
data class VulnerabilityResolution(
3032
val externalId: String,
3133
val reason: String,
32-
val comment: String
34+
val comment: String,
35+
36+
/** The definition of the [VulnerabilityResolution], if available. */
37+
val definition: VulnerabilityResolutionDefinition? = null
3338
)

core/src/main/kotlin/apiDocs/RunsDocs.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,16 @@ import org.eclipse.apoapsis.ortserver.api.v1.model.VulnerabilityRating
5858
import org.eclipse.apoapsis.ortserver.api.v1.model.VulnerabilityReference
5959
import org.eclipse.apoapsis.ortserver.api.v1.model.VulnerabilityResolution
6060
import org.eclipse.apoapsis.ortserver.api.v1.model.VulnerabilityWithDetails
61+
import org.eclipse.apoapsis.ortserver.shared.apimodel.ChangeEvent
62+
import org.eclipse.apoapsis.ortserver.shared.apimodel.ChangeEventAction
6163
import org.eclipse.apoapsis.ortserver.shared.apimodel.PagedResponse
6264
import org.eclipse.apoapsis.ortserver.shared.apimodel.PagedSearchResponse
6365
import org.eclipse.apoapsis.ortserver.shared.apimodel.PagingData
6466
import org.eclipse.apoapsis.ortserver.shared.apimodel.SortDirection
6567
import org.eclipse.apoapsis.ortserver.shared.apimodel.SortProperty
6668
import org.eclipse.apoapsis.ortserver.shared.apimodel.UserDisplayName
69+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionDefinition
70+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionReason
6771
import org.eclipse.apoapsis.ortserver.shared.ktorutils.jsonBody
6872
import org.eclipse.apoapsis.ortserver.shared.ktorutils.standardListQueryParameters
6973

@@ -300,7 +304,21 @@ val getRunVulnerabilities: RouteConfig.() -> Unit = {
300304
VulnerabilityResolution(
301305
externalId = "CVE-2021-1234",
302306
reason = "INEFFECTIVE_VULNERABILITY",
303-
comment = "A comment why the vulnerability can be resolved."
307+
comment = "A comment why the vulnerability can be resolved.",
308+
definition = VulnerabilityResolutionDefinition(
309+
id = 1,
310+
idMatchers = listOf("CVE-2021-1234"),
311+
reason = VulnerabilityResolutionReason.INEFFECTIVE_VULNERABILITY,
312+
comment = "A comment why the vulnerability can be resolved.",
313+
archived = false,
314+
changes = listOf(
315+
ChangeEvent(
316+
user = UserDisplayName(username = "test"),
317+
occurredAt = CREATED_AT,
318+
action = ChangeEventAction.CREATE
319+
)
320+
)
321+
)
304322
)
305323
),
306324
advisor = AdvisorDetails(

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,11 @@ import org.eclipse.apoapsis.ortserver.model.LogSource
101101
import org.eclipse.apoapsis.ortserver.model.OrtRun
102102
import org.eclipse.apoapsis.ortserver.model.OrtRunStatus
103103
import org.eclipse.apoapsis.ortserver.model.PluginConfig
104+
import org.eclipse.apoapsis.ortserver.model.RepositoryId
104105
import org.eclipse.apoapsis.ortserver.model.RepositoryType
105106
import org.eclipse.apoapsis.ortserver.model.Severity
106107
import org.eclipse.apoapsis.ortserver.model.UserDisplayName
108+
import org.eclipse.apoapsis.ortserver.model.VulnerabilityResolutionReason
107109
import org.eclipse.apoapsis.ortserver.model.repositories.OrtRunRepository
108110
import org.eclipse.apoapsis.ortserver.model.runs.AnalyzerConfiguration
109111
import org.eclipse.apoapsis.ortserver.model.runs.Environment
@@ -131,6 +133,8 @@ import org.eclipse.apoapsis.ortserver.shared.apimodel.PagedResponse
131133
import org.eclipse.apoapsis.ortserver.shared.apimodel.PagedSearchResponse
132134
import org.eclipse.apoapsis.ortserver.shared.apimodel.SortDirection
133135
import org.eclipse.apoapsis.ortserver.shared.apimodel.SortProperty
136+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionDefinition
137+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionReason as ApiVulnerabilityResolutionReason
134138
import org.eclipse.apoapsis.ortserver.shared.ktorutils.shouldHaveBody
135139
import org.eclipse.apoapsis.ortserver.storage.Key
136140
import org.eclipse.apoapsis.ortserver.storage.Storage
@@ -811,6 +815,68 @@ class RunsRouteIntegrationTest : AbstractIntegrationTest({
811815
}
812816
}
813817

818+
"return the definition of the vulnerability resolution if it originated from the server" {
819+
integrationTestApplication {
820+
val run1 = dbExtension.fixtures.createOrtRun(repositoryId)
821+
val definitionId = dbExtension.fixtures.createVulnerabilityResolutionDefinition(
822+
RepositoryId(repositoryId),
823+
run1.id,
824+
listOf("CVE-2021-1234"),
825+
VulnerabilityResolutionReason.INVALID_MATCH_VULNERABILITY
826+
)
827+
828+
val run2 = dbExtension.fixtures.createOrtRun(repositoryId)
829+
val advisorJobId = dbExtension.fixtures.createAdvisorJob(run2.id).id
830+
dbExtension.fixtures.createAdvisorRun(advisorJobId, generateAdvisorResult())
831+
832+
val vulnerabilityResolution = VulnerabilityResolution(
833+
"CVE-2018-14721",
834+
"INEFFECTIVE_VULNERABILITY",
835+
"Comment."
836+
)
837+
838+
val repositoryConfiguration =
839+
dbExtension.fixtures.createRepositoryConfiguration(run2.id, listOf(vulnerabilityResolution))
840+
841+
dbExtension.fixtures.resolvedConfigurationRepository.addResolutions(
842+
run2.id,
843+
repositoryConfiguration.resolutions
844+
)
845+
846+
val response = superuserClient.get("/api/v1/runs/${run2.id}/vulnerabilities")
847+
848+
response shouldHaveStatus HttpStatusCode.OK
849+
val vulnerabilities = response.body<PagedResponse<VulnerabilityWithDetails>>()
850+
851+
vulnerabilities.data shouldHaveSize 2
852+
853+
with(vulnerabilities.data.first()) {
854+
vulnerability.externalId shouldBe "CVE-2018-14721"
855+
resolutions shouldHaveSize 1
856+
857+
with(resolutions.first()) {
858+
definition should beNull()
859+
}
860+
}
861+
862+
with(vulnerabilities.data.last()) {
863+
vulnerability.externalId shouldBe "CVE-2021-1234"
864+
resolutions shouldHaveSize 1
865+
866+
with(resolutions.first()) {
867+
definition shouldBe VulnerabilityResolutionDefinition(
868+
id = definitionId,
869+
idMatchers = listOf("CVE-2021-1234"),
870+
reason = ApiVulnerabilityResolutionReason.INVALID_MATCH_VULNERABILITY,
871+
comment = "Comment.",
872+
archived = false,
873+
changes = emptyList()
874+
)
875+
}
876+
}
877+
}
878+
}
879+
814880
"require RepositoryPermission.READ_ORT_RUNS" {
815881
val run = ortRunRepository.create(
816882
repositoryId,

dao/src/testFixtures/kotlin/Fixtures.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ import org.eclipse.apoapsis.ortserver.model.runs.ShortestDependencyPath
7171
import org.eclipse.apoapsis.ortserver.model.runs.VcsInfo
7272
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorConfiguration
7373
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorResult
74+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Curations
75+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Excludes
76+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Includes
77+
import org.eclipse.apoapsis.ortserver.model.runs.repository.LicenseChoices
78+
import org.eclipse.apoapsis.ortserver.model.runs.repository.Resolutions
79+
import org.eclipse.apoapsis.ortserver.model.runs.repository.VulnerabilityResolution
7480

7581
import org.jetbrains.exposed.sql.Database
7682

@@ -299,6 +305,25 @@ class Fixtures(private val db: Database) {
299305
results = results
300306
)
301307

308+
fun createRepositoryConfiguration(
309+
runId: Long = ortRun.id,
310+
vulnerabilityResolutions: List<VulnerabilityResolution> = emptyList()
311+
) = repositoryConfigurationRepository.create(
312+
ortRunId = runId,
313+
analyzerConfig = null,
314+
excludes = Excludes(emptyList(), emptyList()),
315+
includes = Includes(emptyList()),
316+
resolutions = Resolutions(
317+
issues = emptyList(),
318+
ruleViolations = emptyList(),
319+
vulnerabilities = vulnerabilityResolutions
320+
),
321+
curations = Curations(emptyList(), emptyList()),
322+
packageConfigurations = emptyList(),
323+
licenseChoices = LicenseChoices(emptyList(), emptyList()),
324+
provenanceSnippetChoices = emptyList()
325+
)
326+
302327
fun generatePackage(
303328
identifier: Identifier,
304329
authors: Set<String> = emptySet(),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (C) 2025 The ORT Server Authors (See <https://github.com/eclipse-apoapsis/ort-server/blob/main/NOTICE>)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.eclipse.apoapsis.ortserver.model
21+
22+
import org.eclipse.apoapsis.ortserver.model.runs.repository.VulnerabilityResolution
23+
24+
/**
25+
* A data class that represents a [VulnerabilityResolution] that has been applied, along with its definition if
26+
* available.
27+
*/
28+
data class AppliedVulnerabilityResolution(
29+
/** The applied [VulnerabilityResolution]. */
30+
val resolution: VulnerabilityResolution,
31+
32+
/** The definition of the [VulnerabilityResolution], if available. */
33+
val definition: VulnerabilityResolutionDefinition? = null
34+
)

model/src/commonMain/kotlin/VulnerabilityWithDetails.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package org.eclipse.apoapsis.ortserver.model
2222
import org.eclipse.apoapsis.ortserver.model.runs.Identifier
2323
import org.eclipse.apoapsis.ortserver.model.runs.advisor.AdvisorDetails
2424
import org.eclipse.apoapsis.ortserver.model.runs.advisor.Vulnerability
25-
import org.eclipse.apoapsis.ortserver.model.runs.repository.VulnerabilityResolution
2625

2726
/**
2827
* A data class to gather information and related data about a [Vulnerability].
@@ -34,7 +33,7 @@ data class VulnerabilityWithDetails(
3433
/** An advisory rating for the [Vulnerability], derived from the individual references of the [Vulnerability]. */
3534
val rating: VulnerabilityRating,
3635

37-
val resolutions: List<VulnerabilityResolution> = emptyList(),
36+
val resolutions: List<AppliedVulnerabilityResolution> = emptyList(),
3837

3938
/** Details about the used advisor. */
4039
val advisor: AdvisorDetails,

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,25 @@ import org.eclipse.apoapsis.ortserver.dao.repositories.ortrun.OrtRunsTable
3737
import org.eclipse.apoapsis.ortserver.dao.repositories.repository.RepositoriesTable
3838
import org.eclipse.apoapsis.ortserver.dao.repositories.repositoryconfiguration.PackageCurationDataTable
3939
import org.eclipse.apoapsis.ortserver.dao.repositories.repositoryconfiguration.PackageCurationsTable
40+
import org.eclipse.apoapsis.ortserver.dao.repositories.repositoryconfiguration.RepositoryConfigurationsTable
41+
import org.eclipse.apoapsis.ortserver.dao.repositories.repositoryconfiguration.RepositoryConfigurationsVulnerabilityResolutionsTable
42+
import org.eclipse.apoapsis.ortserver.dao.repositories.repositoryconfiguration.VulnerabilityResolutionsTable
4043
import org.eclipse.apoapsis.ortserver.dao.repositories.resolvedconfiguration.ResolvedConfigurationsTable
4144
import org.eclipse.apoapsis.ortserver.dao.repositories.resolvedconfiguration.ResolvedPackageCurationProvidersTable
4245
import org.eclipse.apoapsis.ortserver.dao.repositories.resolvedconfiguration.ResolvedPackageCurationsTable
46+
import org.eclipse.apoapsis.ortserver.dao.tables.VulnerabilityResolutionDefinitionsTable
47+
import org.eclipse.apoapsis.ortserver.dao.tables.VulnerabilityResolutionDefinitionsTable.toVulnerabilityResolutionDefinition
4348
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifierDao
4449
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifiersTable
4550
import org.eclipse.apoapsis.ortserver.dao.utils.applyILike
51+
import org.eclipse.apoapsis.ortserver.model.AppliedVulnerabilityResolution
4652
import org.eclipse.apoapsis.ortserver.model.CountByCategory
4753
import org.eclipse.apoapsis.ortserver.model.VulnerabilityFilters
4854
import org.eclipse.apoapsis.ortserver.model.VulnerabilityForRunsFilters
4955
import org.eclipse.apoapsis.ortserver.model.VulnerabilityRating
5056
import org.eclipse.apoapsis.ortserver.model.VulnerabilityWithAccumulatedData
5157
import org.eclipse.apoapsis.ortserver.model.VulnerabilityWithDetails
58+
import org.eclipse.apoapsis.ortserver.model.runs.repository.VulnerabilityResolution as ModelVulnerabilityResolution
5259
import org.eclipse.apoapsis.ortserver.model.util.ComparisonOperator
5360
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
5461
import org.eclipse.apoapsis.ortserver.model.util.ListQueryResult
@@ -85,7 +92,7 @@ import org.ossreviewtoolkit.model.utils.toPurl
8592
* A service to interact with vulnerabilities.
8693
*/
8794
class VulnerabilityService(private val db: Database, private val ortRunService: OrtRunService) {
88-
fun listForOrtRunId(
95+
suspend fun listForOrtRunId(
8996
ortRunId: Long,
9097
parameters: ListQueryParameters = ListQueryParameters.DEFAULT,
9198
vulnerabilityFilters: VulnerabilityFilters = VulnerabilityFilters()
@@ -159,11 +166,43 @@ class VulnerabilityService(private val db: Database, private val ortRunService:
159166
.drop(parameters.offset?.toInt() ?: 0)
160167
.take(parameters.limit ?: ListQueryParameters.DEFAULT_LIMIT)
161168

169+
val vulnerabilityResolutionDefinitions = db.dbQuery {
170+
RepositoryConfigurationsVulnerabilityResolutionsTable
171+
.innerJoin(VulnerabilityResolutionsTable)
172+
.innerJoin(RepositoryConfigurationsTable)
173+
.innerJoin(VulnerabilityResolutionDefinitionsTable)
174+
.select(
175+
VulnerabilityResolutionsTable.columns + VulnerabilityResolutionDefinitionsTable.columns
176+
)
177+
.where { RepositoryConfigurationsTable.ortRunId eq ortRunId }
178+
.map { row ->
179+
row.toVulnerabilityResolutionDefinition() to ModelVulnerabilityResolution(
180+
row[VulnerabilityResolutionsTable.externalId],
181+
row[VulnerabilityResolutionsTable.reason],
182+
row[VulnerabilityResolutionsTable.comment]
183+
)
184+
}
185+
}
186+
162187
val vulnerabilitiesWithResolutions = limitedVulnerabilities.map { vulnerabilityWithDetails ->
163188
val matchingResolutions = resolutions.filter {
164189
it.matches(vulnerabilityWithDetails.vulnerability.mapToOrt())
165190
}
166-
vulnerabilityWithDetails.copy(resolutions = matchingResolutions.map { it.mapToModel() })
191+
vulnerabilityWithDetails.copy(
192+
resolutions = matchingResolutions.map {
193+
val resolution = it.mapToModel()
194+
195+
val definition = vulnerabilityResolutionDefinitions
196+
.firstOrNull { (_, value) ->
197+
resolution == value
198+
}?.first
199+
200+
AppliedVulnerabilityResolution(
201+
resolution,
202+
definition
203+
)
204+
}
205+
)
167206
}
168207

169208
return ListQueryResult(

0 commit comments

Comments
 (0)