Skip to content

Commit ae25433

Browse files
committed
feat(authorization): Integrate new authorization with routes
Rework the routes defined in the `core` module to use the new `Authorization` component. Signed-off-by: Oliver Heger <[email protected]>
1 parent 0101898 commit ae25433

24 files changed

+797
-786
lines changed

components/authorization/backend/src/main/kotlin/routes/AuthorizedRoutes.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
package org.eclipse.apoapsis.ortserver.components.authorization.routes
2323

24+
import com.auth0.jwk.JwkProviderBuilder
2425
import com.auth0.jwt.interfaces.Payload
2526

2627
import io.github.smiley4.ktoropenapi.config.RouteConfig
@@ -30,8 +31,13 @@ import io.github.smiley4.ktoropenapi.patch
3031
import io.github.smiley4.ktoropenapi.post
3132
import io.github.smiley4.ktoropenapi.put
3233

34+
import io.ktor.server.application.Application
3335
import io.ktor.server.application.ApplicationCall
36+
import io.ktor.server.application.install
37+
import io.ktor.server.auth.Authentication
38+
import io.ktor.server.auth.jwt.jwt
3439
import io.ktor.server.auth.principal
40+
import io.ktor.server.config.ApplicationConfig
3541
import io.ktor.server.routing.Route
3642
import io.ktor.server.routing.RouteSelector
3743
import io.ktor.server.routing.RouteSelectorEvaluation
@@ -40,9 +46,44 @@ import io.ktor.server.routing.RoutingPipelineCall
4046
import io.ktor.server.routing.RoutingResolveContext
4147
import io.ktor.util.AttributeKey
4248

49+
import java.net.URI
50+
import java.util.concurrent.TimeUnit
51+
4352
import org.eclipse.apoapsis.ortserver.components.authorization.rights.EffectiveRole
4453
import org.eclipse.apoapsis.ortserver.components.authorization.service.AuthorizationService
4554

55+
/**
56+
* Configure the authentication for this server application.
57+
*
58+
* This function sets up the Ktor plugins for authentication using JWT tokens. It configures the creation of an
59+
* [OrtServerPrincipal] instance for authorized requests.
60+
*/
61+
fun Application.configureAuthentication(config: ApplicationConfig, authorizationService: AuthorizationService) {
62+
val issuer = config.property("jwt.issuer").getString()
63+
val jwksUri = URI.create(config.property("jwt.jwksUri").getString()).toURL()
64+
val configuredRealm = config.property("jwt.realm").getString()
65+
val requiredAudience = config.property("jwt.audience").getString()
66+
val jwkProvider = JwkProviderBuilder(jwksUri)
67+
.cached(10, 24, TimeUnit.HOURS)
68+
.rateLimited(10, 1, TimeUnit.MINUTES)
69+
.build()
70+
71+
install(Authentication) {
72+
jwt(AuthenticationProviders.TOKEN_PROVIDER) {
73+
realm = configuredRealm
74+
verifier(jwkProvider, issuer) {
75+
acceptLeeway(10)
76+
}
77+
78+
validate { credential ->
79+
credential.payload.takeIf { it.audience.contains(requiredAudience) }?.let {
80+
createAuthorizedPrincipal(authorizationService, credential.payload)
81+
}
82+
}
83+
}
84+
}
85+
}
86+
4687
/**
4788
* Create an [OrtServerPrincipal] for this [ApplicationCall]. If an [AuthorizationChecker] is present in the current
4889
* context, use it to an [EffectiveRole] and perform an authorization check. Result is *null* if this check fails.

components/authorization/backend/src/main/kotlin/routes/OrtServerPrincipal.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package org.eclipse.apoapsis.ortserver.components.authorization.routes
2121

2222
import com.auth0.jwt.interfaces.Payload
2323

24+
import io.ktor.server.application.ApplicationCall
2425
import io.ktor.server.auth.principal
2526
import io.ktor.server.routing.RoutingContext
2627

@@ -110,3 +111,10 @@ class OrtServerPrincipal(
110111
val effectiveRole: EffectiveRole
111112
get() = role ?: throw AuthorizationException()
112113
}
114+
115+
/**
116+
* A convenience extension property to obtain the [OrtServerPrincipal] from an [ApplicationCall] that has already been
117+
* authenticated.
118+
*/
119+
val ApplicationCall.ortServerPrincipal: OrtServerPrincipal
120+
get() = requireNotNull(principal())

core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ dependencies {
7171
requireCapability("$group:routes:$version")
7272
}
7373
}
74+
implementation(projects.components.authorization.backend)
7475
implementation(projects.components.authorizationKeycloak.backend)
7576
implementation(projects.components.infrastructureServices.backend)
7677
implementation(projects.components.infrastructureServices.backend) {

core/src/main/kotlin/api/AdminRoute.kt

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,71 +19,47 @@
1919

2020
package org.eclipse.apoapsis.ortserver.core.api
2121

22-
import io.github.smiley4.ktoropenapi.delete
2322
import io.github.smiley4.ktoropenapi.get
24-
import io.github.smiley4.ktoropenapi.patch
25-
import io.github.smiley4.ktoropenapi.post
2623

2724
import io.ktor.http.HttpStatusCode
2825
import io.ktor.server.request.receive
2926
import io.ktor.server.response.respond
3027
import io.ktor.server.routing.Route
3128
import io.ktor.server.routing.route
3229

33-
import kotlinx.coroutines.Dispatchers
34-
import kotlinx.coroutines.launch
35-
import kotlinx.coroutines.withContext
36-
3730
import org.eclipse.apoapsis.ortserver.api.v1.mapping.mapToApi
3831
import org.eclipse.apoapsis.ortserver.api.v1.model.PatchSection
3932
import org.eclipse.apoapsis.ortserver.api.v1.model.PostUser
40-
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.requireAuthenticated
41-
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.requireSuperuser
42-
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.service.AuthorizationService
43-
import org.eclipse.apoapsis.ortserver.components.authorization.keycloak.service.UserService
33+
import org.eclipse.apoapsis.ortserver.components.authorization.routes.OrtServerPrincipal.Companion.requirePrincipal
34+
import org.eclipse.apoapsis.ortserver.components.authorization.routes.delete
35+
import org.eclipse.apoapsis.ortserver.components.authorization.routes.get
36+
import org.eclipse.apoapsis.ortserver.components.authorization.routes.patch
37+
import org.eclipse.apoapsis.ortserver.components.authorization.routes.post
38+
import org.eclipse.apoapsis.ortserver.components.authorization.routes.requireSuperuser
39+
import org.eclipse.apoapsis.ortserver.components.authorization.service.UserService
4440
import org.eclipse.apoapsis.ortserver.core.apiDocs.deleteUser
4541
import org.eclipse.apoapsis.ortserver.core.apiDocs.getSection
4642
import org.eclipse.apoapsis.ortserver.core.apiDocs.getUsers
4743
import org.eclipse.apoapsis.ortserver.core.apiDocs.patchSection
4844
import org.eclipse.apoapsis.ortserver.core.apiDocs.postUser
49-
import org.eclipse.apoapsis.ortserver.core.apiDocs.runPermissionsSync
5045
import org.eclipse.apoapsis.ortserver.services.ContentManagementService
5146
import org.eclipse.apoapsis.ortserver.shared.ktorutils.requireParameter
5247

5348
import org.koin.ktor.ext.inject
5449

5550
fun Route.admin() = route("admin") {
56-
route("sync-roles") {
57-
val authorizationService by inject<AuthorizationService>()
58-
59-
get(runPermissionsSync) {
60-
requireSuperuser()
61-
62-
withContext(Dispatchers.IO) {
63-
launch {
64-
authorizationService.ensureSuperuserAndSynchronizeRolesAndPermissions()
65-
}
66-
67-
call.respond(HttpStatusCode.Accepted)
68-
}
69-
}
70-
}
7151
/**
7252
* For CRUD operations for users.
7353
*/
7454
route("users") {
7555
val userService by inject<UserService>()
7656

77-
get(getUsers) {
78-
requireSuperuser()
79-
57+
get(getUsers, requireSuperuser()) {
8058
val users = userService.getUsers().map { user -> user.mapToApi() }
8159
call.respond(users)
8260
}
8361

84-
post(postUser) {
85-
requireSuperuser()
86-
62+
post(postUser, requireSuperuser()) {
8763
val createUser = call.receive<PostUser>()
8864
userService.createUser(
8965
username = createUser.username,
@@ -97,9 +73,7 @@ fun Route.admin() = route("admin") {
9773
call.respond(HttpStatusCode.Created)
9874
}
9975

100-
delete(deleteUser) {
101-
requireSuperuser()
102-
76+
delete(deleteUser, requireSuperuser()) {
10377
val username = call.requireParameter("username")
10478
userService.deleteUser(username)
10579

@@ -115,7 +89,7 @@ fun Route.admin() = route("admin") {
11589

11690
route("sections/{sectionId}") {
11791
get(getSection) {
118-
requireAuthenticated()
92+
requirePrincipal()
11993

12094
val id = call.requireParameter("sectionId")
12195

@@ -125,9 +99,7 @@ fun Route.admin() = route("admin") {
12599
call.respond(HttpStatusCode.OK, section.mapToApi())
126100
}
127101

128-
patch(patchSection) {
129-
requireSuperuser()
130-
102+
patch(patchSection, requireSuperuser()) {
131103
val id = call.requireParameter("sectionId")
132104
val updateSection = call.receive<PatchSection>()
133105

0 commit comments

Comments
 (0)