package fr.dcproject.security.voter import fr.dcproject.entity.User import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCallPipeline import io.ktor.application.ApplicationFeature import io.ktor.auth.authentication import io.ktor.http.HttpStatusCode import io.ktor.response.respond import io.ktor.util.AttributeKey import io.ktor.util.KtorExperimentalAPI import io.ktor.util.pipeline.PipelineContext interface ActionI interface Voter { fun supports(action: ActionI, call: ApplicationCall, subject: Any? = null): Boolean fun vote(action: ActionI, call: ApplicationCall, subject: Any? = null): Vote } fun List.can(action: ActionI, call: ApplicationCall, subject: Any? = null): Boolean { val listOfSubject: List = if (subject !is List<*>) listOf(subject) else subject val votes: List = listOfSubject.flatMap { subject -> this .filter { it.supports(action, call, subject) } .ifEmpty { throw NoVoterException(action) } .map { it.vote(action, call, subject) } } return votes.all { it in listOf(Vote.GRANTED, Vote.ABSTAIN) } and votes.any { it == Vote.GRANTED } } enum class Vote { GRANTED, ABSTAIN, DENIED } private val votersAttributeKey = AttributeKey>("voters") fun ApplicationCall.assertCan(action: ActionI, subject: Any? = null) { if (!can(action, subject)) { throw UnauthorizedException(action) } } fun PipelineContext.assertCan(action: ActionI, subject: Any? = null) = context.assertCan(action, subject) fun PipelineContext.can(action: ActionI, subject: Any? = null) = context.can(action, subject) fun ApplicationCall.can(action: ActionI, subject: Any? = null): Boolean { val voters = attributes[votersAttributeKey] return voters.can(action, this, subject) } abstract class VoterException(message: String) : Throwable(message) class NoVoterException(action: ActionI) : VoterException("No voter found for action '$action'") class UnauthorizedException(action: ActionI) : VoterException("Unauthorized for action '$action'") class ForbiddenException(message: String? = null) : Throwable(message) val ApplicationCall.user get() = authentication.principal() class AuthorizationVoter { /** * Configuration for [AuthorizationVoter] feature. */ class Configuration { var voters = mutableListOf() fun voter(voter: Voter) = voters.add(voter) } /** * Object for installing feature */ companion object Feature : ApplicationFeature { override val key = AttributeKey("Voter") @KtorExperimentalAPI override fun install( pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit ): AuthorizationVoter { val configuration = Configuration().apply(configure) pipeline.intercept(ApplicationCallPipeline.Features) { context.attributes.put(votersAttributeKey, configuration.voters) try { proceed() } catch (e: VoterException) { context.respond(HttpStatusCode.Forbidden) } } return AuthorizationVoter() } } }