#24 Move voter code to an external library
This commit is contained in:
@@ -87,6 +87,7 @@ dependencies {
|
|||||||
implementation("com.auth0:java-jwt:3.8.2")
|
implementation("com.auth0:java-jwt:3.8.2")
|
||||||
implementation("com.github.jasync-sql:jasync-postgresql:1.0.7")
|
implementation("com.github.jasync-sql:jasync-postgresql:1.0.7")
|
||||||
implementation("fr.postgresjson:postgresjson:$postgresjson_version")
|
implementation("fr.postgresjson:postgresjson:$postgresjson_version")
|
||||||
|
implementation("fr.ktor-voter:ktor-voter:1.0.0")
|
||||||
implementation("com.sendgrid:sendgrid-java:4.4.1")
|
implementation("com.sendgrid:sendgrid-java:4.4.1")
|
||||||
implementation("io.lettuce:lettuce-core:5.2.2.RELEASE")
|
implementation("io.lettuce:lettuce-core:5.2.2.RELEASE")
|
||||||
implementation("com.rabbitmq:amqp-client:5.8.0")
|
implementation("com.rabbitmq:amqp-client:5.8.0")
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import fr.dcproject.event.EventSubscriber
|
|||||||
import fr.dcproject.event.configEvent
|
import fr.dcproject.event.configEvent
|
||||||
import fr.dcproject.routes.*
|
import fr.dcproject.routes.*
|
||||||
import fr.dcproject.security.voter.*
|
import fr.dcproject.security.voter.*
|
||||||
|
import fr.ktorVoter.AuthorizationVoter
|
||||||
|
import fr.ktorVoter.ForbiddenException
|
||||||
import fr.postgresjson.migration.Migrations
|
import fr.postgresjson.migration.Migrations
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package fr.dcproject
|
package fr.dcproject
|
||||||
|
|
||||||
|
import fr.dcproject.entity.User
|
||||||
import fr.dcproject.entity.UserI
|
import fr.dcproject.entity.UserI
|
||||||
import fr.dcproject.security.voter.ForbiddenException
|
import fr.ktorVoter.ForbiddenException
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.auth.authentication
|
import io.ktor.auth.authentication
|
||||||
import io.ktor.util.AttributeKey
|
import io.ktor.util.AttributeKey
|
||||||
@@ -26,3 +27,5 @@ val ApplicationCall.citizenOrNull: CitizenEntity?
|
|||||||
|
|
||||||
val PipelineContext<Unit, ApplicationCall>.citizen get() = context.citizen
|
val PipelineContext<Unit, ApplicationCall>.citizen get() = context.citizen
|
||||||
val PipelineContext<Unit, ApplicationCall>.citizenOrNull get() = context.citizenOrNull
|
val PipelineContext<Unit, ApplicationCall>.citizenOrNull get() = context.citizenOrNull
|
||||||
|
|
||||||
|
val ApplicationCall.user get() = authentication.principal<User>()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import fr.dcproject.event.ArticleUpdate
|
|||||||
import fr.dcproject.repository.Article.Filter
|
import fr.dcproject.repository.Article.Filter
|
||||||
import fr.dcproject.security.voter.ArticleVoter.Action.CREATE
|
import fr.dcproject.security.voter.ArticleVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.ArticleVoter.Action.VIEW
|
import fr.dcproject.security.voter.ArticleVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import fr.dcproject.views.ArticleViewManager
|
import fr.dcproject.views.ArticleViewManager
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import io.ktor.application.application
|
import io.ktor.application.application
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import fr.dcproject.routes.CitizenPaths.CitizensRequest
|
|||||||
import fr.dcproject.routes.CitizenPaths.CurrentCitizenRequest
|
import fr.dcproject.routes.CitizenPaths.CurrentCitizenRequest
|
||||||
import fr.dcproject.security.voter.CitizenVoter.Action.CHANGE_PASSWORD
|
import fr.dcproject.security.voter.CitizenVoter.Action.CHANGE_PASSWORD
|
||||||
import fr.dcproject.security.voter.CitizenVoter.Action.VIEW
|
import fr.dcproject.security.voter.CitizenVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import fr.postgresjson.repository.RepositoryI.Direction
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.auth.UserPasswordCredential
|
import io.ktor.auth.UserPasswordCredential
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import fr.dcproject.entity.Comment
|
|||||||
import fr.dcproject.entity.CommentRef
|
import fr.dcproject.entity.CommentRef
|
||||||
import fr.dcproject.routes.CommentPaths.CreateCommentRequest.Content
|
import fr.dcproject.routes.CommentPaths.CreateCommentRequest.Content
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.*
|
import fr.dcproject.security.voter.CommentVoter.Action.*
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.features.NotFoundException
|
import io.ktor.features.NotFoundException
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import fr.dcproject.entity.Citizen
|
|||||||
import fr.dcproject.repository.CommentArticle.Sort
|
import fr.dcproject.repository.CommentArticle.Sort
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import fr.dcproject.entity.Citizen
|
|||||||
import fr.dcproject.entity.ConstitutionRef
|
import fr.dcproject.entity.ConstitutionRef
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fr.dcproject.citizen
|
|||||||
import fr.dcproject.entity.request.Constitution
|
import fr.dcproject.entity.request.Constitution
|
||||||
import fr.dcproject.security.voter.ConstitutionVoter.Action.CREATE
|
import fr.dcproject.security.voter.ConstitutionVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.ConstitutionVoter.Action.VIEW
|
import fr.dcproject.security.voter.ConstitutionVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fr.dcproject.citizen
|
|||||||
import fr.dcproject.entity.ArticleRef
|
import fr.dcproject.entity.ArticleRef
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.security.voter.FollowVoter.Action.*
|
import fr.dcproject.security.voter.FollowVoter.Action.*
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.*
|
import io.ktor.locations.*
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fr.dcproject.citizen
|
|||||||
import fr.dcproject.entity.CitizenRef
|
import fr.dcproject.entity.CitizenRef
|
||||||
import fr.dcproject.entity.ConstitutionRef
|
import fr.dcproject.entity.ConstitutionRef
|
||||||
import fr.dcproject.security.voter.FollowVoter.Action.*
|
import fr.dcproject.security.voter.FollowVoter.Action.*
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.*
|
import io.ktor.locations.*
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import fr.dcproject.entity.request.RequestBuilder
|
|||||||
import fr.dcproject.entity.request.getContent
|
import fr.dcproject.entity.request.getContent
|
||||||
import fr.dcproject.security.voter.OpinionVoter.Action.CREATE
|
import fr.dcproject.security.voter.OpinionVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.OpinionVoter.Action.VIEW
|
import fr.dcproject.security.voter.OpinionVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import fr.dcproject.utils.toUUID
|
import fr.dcproject.utils.toUUID
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package fr.dcproject.routes
|
|||||||
|
|
||||||
import fr.dcproject.entity.OpinionChoice
|
import fr.dcproject.entity.OpinionChoice
|
||||||
import fr.dcproject.security.voter.OpinionChoiceVoter.Action.VIEW
|
import fr.dcproject.security.voter.OpinionChoiceVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Location
|
import io.ktor.locations.Location
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import fr.dcproject.routes.VoteArticlePaths.ArticleVoteRequest
|
|||||||
import fr.dcproject.routes.VoteArticlePaths.CommentVoteRequest
|
import fr.dcproject.routes.VoteArticlePaths.CommentVoteRequest
|
||||||
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
|
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.VoteVoter.Action.VIEW
|
import fr.dcproject.security.voter.VoteVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import fr.dcproject.utils.toUUID
|
import fr.dcproject.utils.toUUID
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import fr.dcproject.citizen
|
|||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.routes.VoteConstitutionPaths.ConstitutionVoteRequest.Content
|
import fr.dcproject.routes.VoteConstitutionPaths.ConstitutionVoteRequest.Content
|
||||||
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
|
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import fr.dcproject.security.voter.WorkgroupVoter.Action.UPDATE
|
|||||||
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.ADD as ADD_MEMBERS
|
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.ADD as ADD_MEMBERS
|
||||||
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.UPDATE as UPDATE_MEMBERS
|
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.UPDATE as UPDATE_MEMBERS
|
||||||
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.REMOVE as REMOVE_MEMBERS
|
import fr.dcproject.security.voter.WorkgroupVoter.ActionMembers.REMOVE as REMOVE_MEMBERS
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.ktorVoter.assertCan
|
||||||
import fr.dcproject.utils.toUUID
|
import fr.dcproject.utils.toUUID
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ package fr.dcproject.security.voter
|
|||||||
import fr.dcproject.entity.ArticleAuthI
|
import fr.dcproject.entity.ArticleAuthI
|
||||||
import fr.dcproject.entity.ArticleI
|
import fr.dcproject.entity.ArticleI
|
||||||
import fr.dcproject.entity.UserI
|
import fr.dcproject.entity.UserI
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
|
import fr.ktorVoter.checkClass
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import fr.dcproject.entity.Comment as CommentEntity
|
import fr.dcproject.entity.Comment as CommentEntity
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package fr.dcproject.security.voter
|
|
||||||
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.full.isSubclassOf
|
|
||||||
|
|
||||||
class WrongClassException(
|
|
||||||
expected: KClass<*>,
|
|
||||||
current: KClass<*>?
|
|
||||||
) : VoterException("Can not define authorization with class $current. Need $expected")
|
|
||||||
|
|
||||||
fun Voter.checkClass(
|
|
||||||
expected: KClass<*>,
|
|
||||||
subject: Any?
|
|
||||||
) {
|
|
||||||
if (subject != null && !subject::class.isSubclassOf(expected)) {
|
|
||||||
throw WrongClassException(expected, subject::class)
|
|
||||||
} else if (subject == null) {
|
|
||||||
throw WrongClassException(expected, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,10 @@ package fr.dcproject.security.voter
|
|||||||
|
|
||||||
import fr.dcproject.entity.CitizenBasicI
|
import fr.dcproject.entity.CitizenBasicI
|
||||||
import fr.dcproject.entity.UserI
|
import fr.dcproject.entity.UserI
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.Comment
|
import fr.dcproject.entity.Comment
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
class CommentVoter : Voter {
|
class CommentVoter : Voter {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package fr.dcproject.security.voter
|
|||||||
import fr.dcproject.entity.Comment
|
import fr.dcproject.entity.Comment
|
||||||
import fr.dcproject.entity.ConstitutionSimple
|
import fr.dcproject.entity.ConstitutionSimple
|
||||||
import fr.dcproject.entity.UserI
|
import fr.dcproject.entity.UserI
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import fr.dcproject.entity.Follow as FollowEntity
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
import fr.dcproject.entity.User as UserEntity
|
import fr.dcproject.entity.User as UserEntity
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.OpinionChoice
|
import fr.dcproject.entity.OpinionChoice
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
class OpinionChoiceVoter : Voter {
|
class OpinionChoiceVoter : Voter {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ package fr.dcproject.security.voter
|
|||||||
|
|
||||||
import fr.dcproject.entity.ArticleAuthI
|
import fr.dcproject.entity.ArticleAuthI
|
||||||
import fr.dcproject.entity.Opinion
|
import fr.dcproject.entity.Opinion
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
class OpinionVoter : Voter {
|
class OpinionVoter : Voter {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
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<Voter>.can(action: ActionI, call: ApplicationCall, subject: Any? = null): Boolean {
|
|
||||||
val listOfSubject: List<Any?> = if (subject !is List<*>) listOf(subject) else subject
|
|
||||||
val votes: List<Vote> = 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;
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun isGranted(lambda: () -> Boolean): Vote {
|
|
||||||
return if (lambda()) GRANTED else DENIED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val votersAttributeKey = AttributeKey<List<Voter>>("voters")
|
|
||||||
|
|
||||||
fun ApplicationCall.assertCan(action: ActionI, subject: Any? = null, agreeIfNullOrEmpty: Boolean = true) {
|
|
||||||
val isNullOrEmpty = (subject == null || (subject is Collection<*> && subject.isNullOrEmpty()))
|
|
||||||
if (!can(action, subject) && !agreeIfNullOrEmpty && isNullOrEmpty) {
|
|
||||||
throw UnauthorizedException(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PipelineContext<Unit, ApplicationCall>.assertCan(action: ActionI, subject: Any? = null, agreeIfNullOrEmpty: Boolean = true) =
|
|
||||||
context.assertCan(action, subject, agreeIfNullOrEmpty)
|
|
||||||
|
|
||||||
fun PipelineContext<Unit, ApplicationCall>.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<User>()
|
|
||||||
|
|
||||||
class AuthorizationVoter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for [AuthorizationVoter] feature.
|
|
||||||
*/
|
|
||||||
class Configuration {
|
|
||||||
var voters = mutableListOf<Voter>()
|
|
||||||
fun voter(voter: Voter) = voters.add(voter)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object for installing feature
|
|
||||||
*/
|
|
||||||
companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, AuthorizationVoter> {
|
|
||||||
|
|
||||||
override val key = AttributeKey<AuthorizationVoter>("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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,11 @@ package fr.dcproject.security.voter
|
|||||||
|
|
||||||
import fr.dcproject.citizenOrNull
|
import fr.dcproject.citizenOrNull
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.Voter
|
||||||
|
import fr.ktorVoter.VoterException
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
|
||||||
class WorkgroupVoter : Voter {
|
class WorkgroupVoter : Voter {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.Vote
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
@@ -34,7 +37,7 @@ internal class ArticleVoterTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.CitizenBasic
|
||||||
|
import fr.dcproject.entity.CitizenI
|
||||||
|
import fr.dcproject.entity.User
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -46,7 +52,7 @@ internal class CitizenVoterTest {
|
|||||||
).apply { deletedAt = DateTime.now() }
|
).apply { deletedAt = DateTime.now() }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -66,7 +69,7 @@ internal class CommentVoterTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -56,7 +59,7 @@ internal class FollowVoterTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -40,7 +43,7 @@ internal class OpinionChoiceVoterTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -55,7 +58,7 @@ internal class OpinionVoterTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -12,6 +14,7 @@ import org.joda.time.DateTime
|
|||||||
import org.junit.jupiter.api.Tag
|
import org.junit.jupiter.api.Tag
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
@@ -70,7 +73,7 @@ internal class VoteVoterTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.*
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.user
|
||||||
|
import fr.ktorVoter.ActionI
|
||||||
|
import fr.ktorVoter.VoterException
|
||||||
|
import fr.ktorVoter.can
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
@@ -66,7 +70,7 @@ internal class WorkgroupVoterTest {
|
|||||||
private val workgroupref = WorkgroupRef()
|
private val workgroupref = WorkgroupRef()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mockkStatic("fr.dcproject.security.voter.VoterKt")
|
mockkStatic("fr.dcproject.ApplicationContextKt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user