Joost Klitsie
11/22/2020, 9:38 AMrouting {
withRole(ADMIN) {
route("admin_only_route") {
}
withRole(USER) {
route("special_route") {
}
}
}
}
Joris PZ
11/22/2020, 2:15 PMwithRole
how these two role checks would combine. The implementation I wrote some time ago would (I think, haven't checked) combine these two such that to access special_route
you would need to have both ADMIN
and USER
roles.Joost Klitsie
11/22/2020, 2:45 PMJoris PZ
11/22/2020, 2:54 PMrouting {
withrole("ADMIN) {
route("foo") {
route("xxyyz") {
//
}
}
}
withrole("USER) {
route("foo") {
route("bla") {
//
}
}
}
}
and Ktor would figure it all out (and it has the added benefit of clearly showing which routes are admin-only and which are user)
Otherwise, maybe you could in your interceptor walk up the route tree and delete/cancel any earlier AuthorizedRoutes you find? That would do it tooJoris PZ
11/22/2020, 2:55 PMJoost Klitsie
11/22/2020, 3:33 PMJoost Klitsie
11/22/2020, 3:34 PMJoris PZ
11/22/2020, 4:22 PMwithDefaultRole
extension function on Route
that adds an interceptor in a new phase, say DefaultAuthorizationPhase
, that executes right after your regular AuthorizationPhase
. Then, in your regular authorization interceptor you can add a flag in the PipelineContext
that signals to the downstream interceptor if authorization has already taken place. If it didn't, you can then proceed with your default authorization, if it didn't you can just do nothing.Joost Klitsie
11/22/2020, 5:12 PMfun Route.test(
message: String,
build: Route.() -> Unit,
): Route {
val testRoute = createChild(SimpleSelector("message"))
application.feature(SimpleInterceptor).apply {
interceptPipeline(testRoute, message, countSimpleSelectors())
}
testRoute.build()
return testRoute
}
fun Route.countSimpleSelectors(): Int = children.sumBy { child ->
child.countSimpleSelectors()
} + if (selector is SimpleSelector) 1 else 0
Joost Klitsie
11/22/2020, 5:13 PMJoost Klitsie
11/22/2020, 5:14 PMfun interceptPipeline(
pipeline: ApplicationCallPipeline,
message: String,
counter: Int,
) {
pipeline.insertPhaseAfter(ApplicationCallPipeline.Features, Authentication.ChallengePhase)
val phases = (counter downTo 1).map {
simplePhase(it)
}
phases.fold(Authentication.ChallengePhase) { old, new ->
pipeline.insertPhaseAfter(old, new)
new
}
pipeline.intercept(phases.first()) {
val call = call
val simpleContext = SimpleContext.from(call)
TestPipeline().apply {
val subject = SimpleContext.from(call)
println("original subject: $message, new subject: ${subject.someMessage}")
subject.someMessage = message
}.execute(call, simpleContext)
}
}
And then we just add a lot of phasesJoost Klitsie
11/22/2020, 5:14 PMJoost Klitsie
11/22/2020, 5:14 PMJoost Klitsie
11/22/2020, 5:15 PMJoost Klitsie
11/22/2020, 6:10 PMinstall(AccessRulesInterceptor) {
minimalRole = Role.ADMIN
}
routing {
route("/") {
authenticate("jwt") {
routeWithRules {
// All components registered here
Then my admin account can of course perform all the actions, but also if the user is logged in he/she can do certain things. For example, if the user wants to update the name of the team, it should be allowed to do that:
override fun Route.installRouting() {
routeWithRules(Api.v0.team("{id}"), ruleException = { principal ->
val teamId = call.parameters["id"].toUuidOrNull() ?: return@routeWithRules false
teamService.canUserUpdateTeam(teamId, principal)
}) {
put {
// .. save the new team info somehwere
So here I basically add an exception to the endpoint, allowing a user to update its own team name when calling PUT api/v0/team/<id>
. You can also override the minimal role that you need to perform a certain call, for example allow all users with role USER (or upper) to call certain things. So I am for now pretty happy with this simple api I created, should fit my needs.