Skip to content

Commit a1a1492

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

File tree

19 files changed

+1017
-0
lines changed

19 files changed

+1017
-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 definition.
28+
*/
29+
@Serializable
30+
data class CreateVulnerabilityResolutionDefinition(
31+
/** The ID of the run in which context the vulnerability resolution definition has been 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.postVulnerabilityResolutionDefinition
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+
postVulnerabilityResolutionDefinition(ortRunService, vulnerabilityResolutionDefinitionService)
33+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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.CreateVulnerabilityResolutionDefinition
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.postVulnerabilityResolutionDefinition(
55+
ortRunService: OrtRunService,
56+
vulnerabilityResolutionDefinitionService: VulnerabilityResolutionDefinitionService
57+
) = post("/resolutions/vulnerabilities", {
58+
operationId = "postVulnerabilityResolutionDefinition"
59+
summary = "Create a vulnerability resolution definition"
60+
tags = listOf("Resolutions")
61+
62+
request {
63+
jsonBody<CreateVulnerabilityResolutionDefinition> {
64+
example("Create Vulnerability Resolution Definition") {
65+
value = CreateVulnerabilityResolutionDefinition(
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<CreateVulnerabilityResolutionDefinition>()
100+
101+
val repositoryId = ortRunService.getRepositoryIdForOrtRun(createResolution.contextRunId)
102+
103+
if (repositoryId == null) throw AuthorizationException()
104+
105+
requirePermission(RepositoryPermission.WRITE.roleName(repositoryId))
106+
107+
// Extract the user information from the principal.
108+
val userDisplayName = call.principal<OrtPrincipal>()?.let { principal ->
109+
ModelUserDisplayName(principal.getUserId(), principal.getUsername(), principal.getFullName())
110+
}
111+
112+
if (userDisplayName == null) {
113+
call.respondError(HttpStatusCode.InternalServerError, "Unable to resolve user display name from token.")
114+
return@post
115+
}
116+
117+
val vulnerabilityResolutionDefinition = vulnerabilityResolutionDefinitionService.create(
118+
RepositoryId(repositoryId),
119+
createResolution.contextRunId,
120+
userDisplayName,
121+
createResolution.idMatchers,
122+
createResolution.reason.mapToModel(),
123+
createResolution.comment
124+
).mapToApi()
125+
126+
call.respond(HttpStatusCode.Created, vulnerabilityResolutionDefinition)
127+
}

0 commit comments

Comments
 (0)