Skip to content

Commit 0b9c1f3

Browse files
committed
feat: Allow to add vulnerability resolutions
Add support for creating vulnerability resolutions on the server. In the initial implementation, the resolutions are created in the scope of the repository. Relates to #1009. Signed-off-by: Johanna Lamppu <[email protected]>
1 parent 54670d4 commit 0b9c1f3

File tree

19 files changed

+1023
-0
lines changed

19 files changed

+1023
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.components.resolutions
21+
22+
import kotlinx.serialization.Serializable
23+
24+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionReason
25+
26+
/**
27+
* The request object for creating a vulnerability resolution.
28+
*/
29+
@Serializable
30+
data class PostVulnerabilityResolution(
31+
/** The ID of the run in which context the vulnerability resolution is made in. */
32+
val contextRunId: Long,
33+
34+
/**
35+
* The list of vulnerability ID matchers (regular expressions) to match the ids of the vulnerabilities to resolve.
36+
*/
37+
val idMatchers: List<String>,
38+
39+
/** The reason why the vulnerability is resolved. */
40+
val reason: VulnerabilityResolutionReason,
41+
42+
/** A comment to further explain why the [reason] is applicable here. */
43+
val comment: String
44+
)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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.components.resolutions
21+
22+
import org.eclipse.apoapsis.ortserver.dao.dbQuery
23+
import org.eclipse.apoapsis.ortserver.dao.repositories.userDisplayName.UserDisplayNameDao
24+
import org.eclipse.apoapsis.ortserver.dao.tables.ChangeLogTable
25+
import org.eclipse.apoapsis.ortserver.dao.tables.VulnerabilityResolutionDefinitionsTable
26+
import org.eclipse.apoapsis.ortserver.model.ChangeEventAction
27+
import org.eclipse.apoapsis.ortserver.model.ChangeEventEntityType
28+
import org.eclipse.apoapsis.ortserver.model.RepositoryId
29+
import org.eclipse.apoapsis.ortserver.model.UserDisplayName
30+
import org.eclipse.apoapsis.ortserver.model.VulnerabilityResolutionDefinition
31+
import org.eclipse.apoapsis.ortserver.model.VulnerabilityResolutionReason
32+
import org.eclipse.apoapsis.ortserver.services.ortrun.OrtRunService
33+
34+
import org.jetbrains.exposed.sql.Database
35+
36+
/**
37+
* Service class for managing vulnerability resolution definitions.
38+
*/
39+
class VulnerabilityResolutionDefinitionService(private val db: Database, private val ortRunService: OrtRunService) {
40+
suspend fun create(
41+
hierarchyId: RepositoryId,
42+
contextRunId: Long,
43+
userDisplayName: UserDisplayName,
44+
idMatchers: List<String>,
45+
reason: VulnerabilityResolutionReason,
46+
comment: String
47+
): VulnerabilityResolutionDefinition = db.dbQuery {
48+
val id = VulnerabilityResolutionDefinitionsTable.insert(
49+
hierarchyId,
50+
contextRunId,
51+
idMatchers,
52+
reason,
53+
comment
54+
)
55+
56+
addChangeLogEvent(id, ChangeEventAction.CREATE, userDisplayName)
57+
58+
ortRunService.markAsOutdated(listOf(contextRunId), "New vulnerability resolution added.")
59+
60+
VulnerabilityResolutionDefinitionsTable.get(id)
61+
}
62+
63+
private fun addChangeLogEvent(
64+
entityId: Long,
65+
action: ChangeEventAction,
66+
userDisplayName: UserDisplayName
67+
) {
68+
val user =
69+
UserDisplayNameDao.insertOrUpdate(userDisplayName) ?: throw NullPointerException("No user created or found")
70+
71+
ChangeLogTable.insert(
72+
ChangeEventEntityType.VULNERABILITY_RESOLUTION_DEFINITION,
73+
entityId.toString(),
74+
user.id.value,
75+
action
76+
)
77+
}
78+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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.components.resolutions
21+
22+
import io.ktor.server.routing.Route
23+
24+
import org.eclipse.apoapsis.ortserver.components.resolutions.routes.vulnerabilities.postVulnerabilityResolution
25+
import org.eclipse.apoapsis.ortserver.services.ortrun.OrtRunService
26+
27+
/** Add all resolutions routes. */
28+
fun Route.resolutionsRoutes(
29+
ortRunService: OrtRunService,
30+
vulnerabilityResolutionDefinitionService: VulnerabilityResolutionDefinitionService
31+
) {
32+
postVulnerabilityResolution(ortRunService, vulnerabilityResolutionDefinitionService)
33+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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.components.resolutions.routes.vulnerabilities
21+
22+
import io.github.smiley4.ktoropenapi.post
23+
24+
import io.ktor.http.HttpStatusCode
25+
import io.ktor.server.auth.principal
26+
import io.ktor.server.request.receive
27+
import io.ktor.server.response.respond
28+
import io.ktor.server.routing.Route
29+
30+
import kotlinx.datetime.Instant
31+
32+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.AuthorizationException
33+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.OrtPrincipal
34+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.getFullName
35+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.getUserId
36+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.getUsername
37+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.permissions.RepositoryPermission
38+
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.requirePermission
39+
import org.eclipse.apoapsis.ortserver.components.resolutions.PostVulnerabilityResolution
40+
import org.eclipse.apoapsis.ortserver.components.resolutions.VulnerabilityResolutionDefinitionService
41+
import org.eclipse.apoapsis.ortserver.model.RepositoryId
42+
import org.eclipse.apoapsis.ortserver.model.UserDisplayName as ModelUserDisplayName
43+
import org.eclipse.apoapsis.ortserver.services.ortrun.OrtRunService
44+
import org.eclipse.apoapsis.ortserver.shared.apimappings.mapToApi
45+
import org.eclipse.apoapsis.ortserver.shared.apimappings.mapToModel
46+
import org.eclipse.apoapsis.ortserver.shared.apimodel.ChangeEvent
47+
import org.eclipse.apoapsis.ortserver.shared.apimodel.ChangeEventAction
48+
import org.eclipse.apoapsis.ortserver.shared.apimodel.UserDisplayName
49+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionDefinition
50+
import org.eclipse.apoapsis.ortserver.shared.apimodel.VulnerabilityResolutionReason
51+
import org.eclipse.apoapsis.ortserver.shared.ktorutils.jsonBody
52+
import org.eclipse.apoapsis.ortserver.shared.ktorutils.respondError
53+
54+
internal fun Route.postVulnerabilityResolution(
55+
ortRunService: OrtRunService,
56+
vulnerabilityResolutionDefinitionService: VulnerabilityResolutionDefinitionService
57+
) = post("/resolutions/vulnerabilities", {
58+
operationId = "postVulnerabilityResolution"
59+
summary = "Create a vulnerability resolution"
60+
tags = listOf("Resolutions")
61+
62+
request {
63+
jsonBody<PostVulnerabilityResolution> {
64+
example("Create Vulnerability Resolution") {
65+
value = PostVulnerabilityResolution(
66+
contextRunId = 1,
67+
idMatchers = listOf("CVE-2020-15250", "GHSA-269g-pwp5-87pp"),
68+
reason = VulnerabilityResolutionReason.INEFFECTIVE_VULNERABILITY,
69+
comment = "Comment"
70+
)
71+
}
72+
}
73+
}
74+
75+
response {
76+
HttpStatusCode.Created to {
77+
description = "Success"
78+
jsonBody<VulnerabilityResolutionDefinition> {
79+
example("Create Vulnerability Resolution") {
80+
value = VulnerabilityResolutionDefinition(
81+
id = 1,
82+
idMatchers = listOf("CVE-2020-15250", "GHSA-269g-pwp5-87pp"),
83+
reason = VulnerabilityResolutionReason.INEFFECTIVE_VULNERABILITY,
84+
comment = "Comment",
85+
archived = false,
86+
changes = listOf(
87+
ChangeEvent(
88+
user = UserDisplayName(username = "User"),
89+
occurredAt = Instant.parse("2024-01-01T00:00:00Z"),
90+
ChangeEventAction.CREATE
91+
)
92+
)
93+
)
94+
}
95+
}
96+
}
97+
}
98+
}) {
99+
val createResolution = call.receive<PostVulnerabilityResolution>()
100+
101+
val repositoryId = ortRunService.getRepositoryIdForOrtRun(createResolution.contextRunId)
102+
?: throw AuthorizationException()
103+
104+
requirePermission(RepositoryPermission.WRITE.roleName(repositoryId))
105+
106+
// Extract the user information from the principal.
107+
val userDisplayName = call.principal<OrtPrincipal>()?.let { principal ->
108+
ModelUserDisplayName(principal.getUserId(), principal.getUsername(), principal.getFullName())
109+
}
110+
111+
if (userDisplayName == null) {
112+
call.respondError(HttpStatusCode.InternalServerError, "Unable to resolve user display name from token.")
113+
return@post
114+
}
115+
116+
val vulnerabilityResolutionDefinition = vulnerabilityResolutionDefinitionService.create(
117+
RepositoryId(repositoryId),
118+
createResolution.contextRunId,
119+
userDisplayName,
120+
createResolution.idMatchers,
121+
createResolution.reason.mapToModel(),
122+
createResolution.comment
123+
).mapToApi()
124+
125+
call.respond(HttpStatusCode.Created, vulnerabilityResolutionDefinition)
126+
}

0 commit comments

Comments
 (0)