Skip to content

Commit 5f2aafd

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. Signed-off-by: Johanna Lamppu <[email protected]>
1 parent bc4aaad commit 5f2aafd

File tree

9 files changed

+309
-8
lines changed

9 files changed

+309
-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: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,24 @@ 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
4347
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifierDao
4448
import org.eclipse.apoapsis.ortserver.dao.tables.shared.IdentifiersTable
4549
import org.eclipse.apoapsis.ortserver.dao.utils.applyILike
50+
import org.eclipse.apoapsis.ortserver.model.AppliedVulnerabilityResolution
4651
import org.eclipse.apoapsis.ortserver.model.CountByCategory
4752
import org.eclipse.apoapsis.ortserver.model.VulnerabilityFilters
4853
import org.eclipse.apoapsis.ortserver.model.VulnerabilityForRunsFilters
4954
import org.eclipse.apoapsis.ortserver.model.VulnerabilityRating
5055
import org.eclipse.apoapsis.ortserver.model.VulnerabilityWithAccumulatedData
5156
import org.eclipse.apoapsis.ortserver.model.VulnerabilityWithDetails
57+
import org.eclipse.apoapsis.ortserver.model.runs.repository.VulnerabilityResolution as ModelVulnerabilityResolution
5258
import org.eclipse.apoapsis.ortserver.model.util.ComparisonOperator
5359
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
5460
import org.eclipse.apoapsis.ortserver.model.util.ListQueryResult
@@ -85,7 +91,7 @@ import org.ossreviewtoolkit.model.utils.toPurl
8591
* A service to interact with vulnerabilities.
8692
*/
8793
class VulnerabilityService(private val db: Database, private val ortRunService: OrtRunService) {
88-
fun listForOrtRunId(
94+
suspend fun listForOrtRunId(
8995
ortRunId: Long,
9096
parameters: ListQueryParameters = ListQueryParameters.DEFAULT,
9197
vulnerabilityFilters: VulnerabilityFilters = VulnerabilityFilters()
@@ -159,11 +165,61 @@ class VulnerabilityService(private val db: Database, private val ortRunService:
159165
.drop(parameters.offset?.toInt() ?: 0)
160166
.take(parameters.limit ?: ListQueryParameters.DEFAULT_LIMIT)
161167

168+
val vulnerabilityResolutionDefinitions = db.dbQuery {
169+
RepositoryConfigurationsVulnerabilityResolutionsTable
170+
.innerJoin(VulnerabilityResolutionsTable)
171+
.innerJoin(RepositoryConfigurationsTable)
172+
.select(
173+
VulnerabilityResolutionsTable.externalId,
174+
VulnerabilityResolutionsTable.reason,
175+
VulnerabilityResolutionsTable.comment,
176+
RepositoryConfigurationsVulnerabilityResolutionsTable.vulnerabilityResolutionDefinitionId
177+
)
178+
.where {
179+
(RepositoryConfigurationsTable.ortRunId eq ortRunId) and
180+
(
181+
RepositoryConfigurationsVulnerabilityResolutionsTable.vulnerabilityResolutionDefinitionId
182+
.isNotNull()
183+
)
184+
}
185+
.map { row ->
186+
val definitionId = requireNotNull(
187+
row[RepositoryConfigurationsVulnerabilityResolutionsTable.vulnerabilityResolutionDefinitionId]
188+
) {
189+
"vulnerabilityResolutionDefinitionId should never be null due to where clause."
190+
}
191+
192+
definitionId.value to ModelVulnerabilityResolution(
193+
row[VulnerabilityResolutionsTable.externalId],
194+
row[VulnerabilityResolutionsTable.reason],
195+
row[VulnerabilityResolutionsTable.comment]
196+
)
197+
}
198+
}
199+
162200
val vulnerabilitiesWithResolutions = limitedVulnerabilities.map { vulnerabilityWithDetails ->
163201
val matchingResolutions = resolutions.filter {
164202
it.matches(vulnerabilityWithDetails.vulnerability.mapToOrt())
165203
}
166-
vulnerabilityWithDetails.copy(resolutions = matchingResolutions.map { it.mapToModel() })
204+
vulnerabilityWithDetails.copy(
205+
resolutions = matchingResolutions.map {
206+
val resolution = it.mapToModel()
207+
208+
val definitionId: Long? = vulnerabilityResolutionDefinitions
209+
.firstOrNull { (_, value) ->
210+
resolution == value
211+
}?.first
212+
213+
AppliedVulnerabilityResolution(
214+
resolution,
215+
definitionId?.let {
216+
db.dbQuery {
217+
VulnerabilityResolutionDefinitionsTable.get(definitionId)
218+
}
219+
}
220+
)
221+
}
222+
)
167223
}
168224

169225
return ListQueryResult(

0 commit comments

Comments
 (0)