Refactoring of FollowVoter

This commit is contained in:
2021-01-17 23:46:51 +01:00
parent d6840e8064
commit 55cd97078a
6 changed files with 67 additions and 123 deletions

View File

@@ -96,7 +96,6 @@ fun Application.module(env: Env = PROD) {
install(AuthorizationVoter) {
voters = listOf(
FollowVoter(),
OpinionVoter(),
OpinionChoiceVoter()
)
@@ -207,8 +206,8 @@ fun Application.module(env: Env = PROD) {
updateMemberOfWorkgroup(get(), get())
/* TODO */
constitution(get(), get())
followArticle(get())
followConstitution(get())
followArticle(get(), get())
followConstitution(get(), get())
commentConstitution(get(), get())
voteArticle(get(), get(), get(), get())
voteConstitution(get(), get())

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.FollowVoter
import fr.dcproject.security.voter.VoteVoter
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester
@@ -127,6 +128,7 @@ val KoinModule = module {
single { WorkgroupVoter() }
single { ConstitutionVoter() }
single { VoteVoter() }
single { FollowVoter() }
// Elasticsearch Client
single<RestClient> {

View File

@@ -2,11 +2,11 @@ package fr.dcproject.routes
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.entity.FollowForUpdate
import fr.dcproject.security.voter.FollowVoter.Action.*
import fr.ktorVoter.assertCan
import fr.ktorVoter.assertCanAll
import fr.dcproject.security.voter.FollowVoter
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
@@ -24,24 +24,24 @@ object FollowArticlePaths {
}
@KtorExperimentalLocationsAPI
fun Route.followArticle(repo: FollowArticleRepository) {
fun Route.followArticle(repo: FollowArticleRepository, voter: FollowVoter) {
post<FollowArticlePaths.ArticleFollowRequest> {
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
assertCan(CREATE, follow)
voter.assert { canCreate(follow, citizenOrNull) }
repo.follow(follow)
call.respond(HttpStatusCode.Created)
}
delete<FollowArticlePaths.ArticleFollowRequest> {
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
assertCan(DELETE, follow)
voter.assert { canDelete(follow, citizenOrNull) }
repo.unfollow(follow)
call.respond(HttpStatusCode.NoContent)
}
get<FollowArticlePaths.ArticleFollowRequest> {
repo.findFollow(citizen, it.article)?.let { follow ->
assertCan(VIEW, follow)
voter.assert { canView(follow, citizenOrNull) }
call.respond(follow)
} ?: call.respond(HttpStatusCode.NoContent)
}
@@ -49,7 +49,7 @@ fun Route.followArticle(repo: FollowArticleRepository) {
get<FollowArticlePaths.CitizenFollowArticleRequest> {
val follows = repo.findByCitizen(it.citizen)
if (follows.result.isNotEmpty()) {
assertCanAll(VIEW, follows.result)
voter.assert { canView(follows.result, citizenOrNull) }
}
call.respond(follows)
}

View File

@@ -1,12 +1,12 @@
package fr.dcproject.routes
import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.entity.ConstitutionRef
import fr.dcproject.entity.FollowForUpdate
import fr.dcproject.security.voter.FollowVoter.Action.*
import fr.ktorVoter.assertCan
import fr.ktorVoter.assertCanAll
import fr.dcproject.security.voter.FollowVoter
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
@@ -24,31 +24,31 @@ object FollowConstitutionPaths {
}
@KtorExperimentalLocationsAPI
fun Route.followConstitution(repo: FollowConstitutionRepository) {
fun Route.followConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
post<FollowConstitutionPaths.ConstitutionFollowRequest> {
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
assertCan(CREATE, follow)
voter.assert { canCreate(follow, citizenOrNull) }
repo.follow(follow)
call.respond(HttpStatusCode.Created)
}
delete<FollowConstitutionPaths.ConstitutionFollowRequest> {
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
assertCan(DELETE, follow)
voter.assert { canDelete(follow, citizenOrNull) }
repo.unfollow(follow)
call.respond(HttpStatusCode.NoContent)
}
get<FollowConstitutionPaths.ConstitutionFollowRequest> {
repo.findFollow(citizen, it.constitution)?.let { follow ->
assertCan(VIEW, follow)
voter.assert { canView(follow, citizenOrNull) }
call.respond(follow)
} ?: call.respond(HttpStatusCode.NotFound)
}
get<FollowConstitutionPaths.CitizenFollowConstitutionRequest> {
val follows = repo.findByCitizen(it.citizen)
assertCanAll(VIEW, follows.result)
voter.assert { canView(follows.result, citizenOrNull) }
call.respond(follows)
}
}

View File

@@ -1,46 +1,26 @@
package fr.dcproject.security.voter
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.FollowI
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.*
import io.ktor.application.*
import fr.dcproject.voter.Voter
import fr.dcproject.voter.VoterResponse
import fr.dcproject.entity.Follow as FollowEntity
class FollowVoter : Voter<ApplicationCall> {
enum class Action : ActionI {
CREATE,
DELETE,
VIEW
class FollowVoter : Voter() {
fun canCreate(subject: FollowI, citizen: CitizenI?): VoterResponse {
return if (citizen == null) denied("You must be connected to follow", "follow.create.notConnected")
else granted()
}
override fun invoke(action: Any, context: ApplicationCall, subject: Any?): VoterResponseI {
if (action !is Action) return abstain()
if (subject !is FollowI) throw NoSubjectDefinedException(action)
val citizen = context.citizenOrNull
if (action == Action.CREATE) {
return if (citizen == null) denied("You must be connected to follow", "follow.create.notConnected")
else granted()
}
if (action == Action.DELETE) {
return if (citizen == null) denied("You must be connected to unfollow", "follow.delete.notConnected")
else granted()
}
if (action == Action.VIEW) {
if (subject is FollowEntity<*>) {
return voteView(citizen, subject)
}
throw NoSubjectDefinedException(action)
}
return abstain()
fun canDelete(subject: FollowI, citizen: CitizenI?): VoterResponse {
return if (citizen == null) denied("You must be connected to unfollow", "follow.delete.notConnected")
else granted()
}
private fun voteView(citizen: CitizenI?, subject: FollowEntity<*>): VoterResponseI {
fun <S : FollowEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse =
canAll(subjects) { canView(it, citizen) }
fun canView(subject: FollowEntity<*>, citizen: CitizenI?): VoterResponse {
return if ((citizen != null && subject.createdBy.id == citizen.id) || !subject.createdBy.followAnonymous) granted()
else denied("You cannot view an anonymous follow", "follow.view.anonymous")
}