Refactoring of VoteVoter

This commit is contained in:
2021-01-17 23:32:43 +01:00
parent 308a284280
commit d6840e8064
6 changed files with 57 additions and 144 deletions

View File

@@ -96,7 +96,6 @@ fun Application.module(env: Env = PROD) {
install(AuthorizationVoter) {
voters = listOf(
VoteVoter(),
FollowVoter(),
OpinionVoter(),
OpinionChoiceVoter()
@@ -211,8 +210,8 @@ fun Application.module(env: Env = PROD) {
followArticle(get())
followConstitution(get())
commentConstitution(get(), get())
voteArticle(get(), get(), get())
voteConstitution(get())
voteArticle(get(), get(), get(), get())
voteConstitution(get(), get())
opinionArticle(get())
opinionChoice(get())
definition()

View File

@@ -24,6 +24,7 @@ import fr.dcproject.messages.Mailer
import fr.dcproject.messages.NotificationEmailSender
import fr.dcproject.repository.CommentConstitutionRepository
import fr.dcproject.security.voter.ConstitutionVoter
import fr.dcproject.security.voter.VoteVoter
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations
@@ -125,6 +126,7 @@ val KoinModule = module {
single { CommentVoter() }
single { WorkgroupVoter() }
single { ConstitutionVoter() }
single { VoteVoter() }
// Elasticsearch Client
single<RestClient> {

View File

@@ -2,17 +2,16 @@ package fr.dcproject.routes
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.entity.VoteForUpdate
import fr.dcproject.repository.VoteComment
import fr.dcproject.routes.VoteArticlePaths.ArticleVoteRequest
import fr.dcproject.routes.VoteArticlePaths.CommentVoteRequest
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
import fr.dcproject.security.voter.VoteVoter.Action.VIEW
import fr.dcproject.security.voter.VoteVoter
import fr.dcproject.utils.toUUID
import fr.ktorVoter.assertCan
import fr.ktorVoter.assertCanAll
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
@@ -49,7 +48,7 @@ object VoteArticlePaths {
}
@KtorExperimentalLocationsAPI
fun Route.voteArticle(repo: VoteArticleRepository, voteCommentRepo: VoteComment, commentRepo: CommentRepository) {
fun Route.voteArticle(repo: VoteArticleRepository, voteCommentRepo: VoteComment, commentRepo: CommentRepository, voter: VoteVoter) {
put<ArticleVoteRequest> {
val content = call.receive<ArticleVoteRequest.Content>()
val vote = VoteForUpdate(
@@ -57,7 +56,7 @@ fun Route.voteArticle(repo: VoteArticleRepository, voteCommentRepo: VoteComment,
note = content.note,
createdBy = this.citizen
)
assertCan(CREATE, vote)
voter.assert { canCreate(vote, citizenOrNull) }
val votes = repo.vote(vote)
call.respond(HttpStatusCode.Created, votes)
}
@@ -70,14 +69,14 @@ fun Route.voteArticle(repo: VoteArticleRepository, voteCommentRepo: VoteComment,
note = content.note,
createdBy = this.citizen
)
assertCan(CREATE, vote)
voter.assert { canCreate(vote, citizenOrNull) }
val votes = voteCommentRepo.vote(vote)
call.respond(HttpStatusCode.Created, votes)
}
get<VoteArticlePaths.CitizenVoteArticleRequest> {
val votes = repo.findByCitizen(it.citizen, it.page, it.limit)
assertCanAll(VIEW, votes.result)
voter.assert { canView(votes.result, citizenOrNull) }
call.respond(votes)
}
@@ -85,7 +84,7 @@ fun Route.voteArticle(repo: VoteArticleRepository, voteCommentRepo: VoteComment,
get<VoteArticlePaths.CitizenVotesByIdsRequest> {
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
if (votes.isNotEmpty()) {
assertCanAll(VIEW, votes)
voter.assert { canView(votes, citizenOrNull) }
}
call.respond(votes)
}

View File

@@ -1,11 +1,12 @@
package fr.dcproject.routes
import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.entity.VoteForUpdate
import fr.dcproject.routes.VoteConstitutionPaths.ConstitutionVoteRequest.Content
import fr.dcproject.security.voter.VoteVoter.Action.CREATE
import fr.ktorVoter.assertCan
import fr.dcproject.security.voter.VoteVoter
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
@@ -27,7 +28,7 @@ object VoteConstitutionPaths {
}
@KtorExperimentalLocationsAPI
fun Route.voteConstitution(repo: VoteConstitutionRepository) {
fun Route.voteConstitution(repo: VoteConstitutionRepository, voter: VoteVoter) {
put<VoteConstitutionPaths.ConstitutionVoteRequest> {
val content = call.receive<Content>()
val vote = VoteForUpdate(
@@ -35,7 +36,7 @@ fun Route.voteConstitution(repo: VoteConstitutionRepository) {
note = content.note,
createdBy = this.citizen
)
assertCan(CREATE, vote)
voter.assert { canCreate(vote, citizenOrNull) }
repo.vote(vote)
call.respond(HttpStatusCode.Created)
}

View File

@@ -1,50 +1,26 @@
package fr.dcproject.security.voter
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.TargetI
import fr.dcproject.entity.VoteForUpdateI
import fr.dcproject.entity.VoteI
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.*
import fr.dcproject.voter.Voter
import fr.dcproject.voter.VoterResponse
import fr.postgresjson.entity.EntityDeletedAt
import io.ktor.application.*
import fr.dcproject.entity.Vote as VoteEntity
class VoteVoter : Voter<ApplicationCall> {
enum class Action : ActionI {
CREATE,
VIEW
class VoteVoter : Voter() {
fun <S> canCreate(subject: VoteForUpdateI<S, *>, citizen: CitizenI?): VoterResponse where S : EntityDeletedAt, S : TargetI = when {
citizen == null -> denied("You must be connected for vote", "vote.create.connected")
subject.target.isDeleted() -> denied("You cannot vote on deleted target", "vote.create.isDeleted")
else -> granted()
}
override fun invoke(action: Any, context: ApplicationCall, subject: Any?): VoterResponseI {
if ((action is Action && subject == null)) throw NoSubjectDefinedException(action)
if (!(action is Action && subject is VoteI)) return abstain()
fun <S : VoteEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse =
canAll(subjects) { canView(it, citizen) }
val citizen = context.citizenOrNull ?: return denied("You must be connected for vote", "vote.connected")
if (action == Action.CREATE) {
if (subject !is VoteForUpdateI<*, *>) throw NoSubjectDefinedException(action)
subject.target.let {
if (it is EntityDeletedAt) {
if (it.isDeleted()) return denied("You cannot vote on deleted target", "vote.create.isDeleted")
} else {
throw NoSubjectDefinedException(action)
}
}
return granted()
}
if (action == Action.VIEW) {
if (subject is VoteEntity<*>) {
return if (subject.createdBy.id != citizen.id) {
denied("You can view only your votes", "vote.view")
} else {
granted()
}
} else {
throw NoSubjectDefinedException(action)
}
}
return abstain()
fun canView(subject: VoteEntity<*>, citizen: CitizenI?): VoterResponse = when {
citizen == null -> denied("You must be connected for view your votes", "vote.view.connected")
subject.createdBy.id != citizen.id -> denied("You can only display your votes", "vote.view.onlyYours")
else -> granted()
}
}