Refactoring of FollowVoter
This commit is contained in:
@@ -96,7 +96,6 @@ fun Application.module(env: Env = PROD) {
|
|||||||
|
|
||||||
install(AuthorizationVoter) {
|
install(AuthorizationVoter) {
|
||||||
voters = listOf(
|
voters = listOf(
|
||||||
FollowVoter(),
|
|
||||||
OpinionVoter(),
|
OpinionVoter(),
|
||||||
OpinionChoiceVoter()
|
OpinionChoiceVoter()
|
||||||
)
|
)
|
||||||
@@ -207,8 +206,8 @@ fun Application.module(env: Env = PROD) {
|
|||||||
updateMemberOfWorkgroup(get(), get())
|
updateMemberOfWorkgroup(get(), get())
|
||||||
/* TODO */
|
/* TODO */
|
||||||
constitution(get(), get())
|
constitution(get(), get())
|
||||||
followArticle(get())
|
followArticle(get(), get())
|
||||||
followConstitution(get())
|
followConstitution(get(), get())
|
||||||
commentConstitution(get(), get())
|
commentConstitution(get(), get())
|
||||||
voteArticle(get(), get(), get(), get())
|
voteArticle(get(), get(), get(), get())
|
||||||
voteConstitution(get(), get())
|
voteConstitution(get(), get())
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import fr.dcproject.messages.Mailer
|
|||||||
import fr.dcproject.messages.NotificationEmailSender
|
import fr.dcproject.messages.NotificationEmailSender
|
||||||
import fr.dcproject.repository.CommentConstitutionRepository
|
import fr.dcproject.repository.CommentConstitutionRepository
|
||||||
import fr.dcproject.security.voter.ConstitutionVoter
|
import fr.dcproject.security.voter.ConstitutionVoter
|
||||||
|
import fr.dcproject.security.voter.FollowVoter
|
||||||
import fr.dcproject.security.voter.VoteVoter
|
import fr.dcproject.security.voter.VoteVoter
|
||||||
import fr.postgresjson.connexion.Connection
|
import fr.postgresjson.connexion.Connection
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
@@ -127,6 +128,7 @@ val KoinModule = module {
|
|||||||
single { WorkgroupVoter() }
|
single { WorkgroupVoter() }
|
||||||
single { ConstitutionVoter() }
|
single { ConstitutionVoter() }
|
||||||
single { VoteVoter() }
|
single { VoteVoter() }
|
||||||
|
single { FollowVoter() }
|
||||||
|
|
||||||
// Elasticsearch Client
|
// Elasticsearch Client
|
||||||
single<RestClient> {
|
single<RestClient> {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package fr.dcproject.routes
|
|||||||
|
|
||||||
import fr.dcproject.component.article.ArticleRef
|
import fr.dcproject.component.article.ArticleRef
|
||||||
import fr.dcproject.component.auth.citizen
|
import fr.dcproject.component.auth.citizen
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
import fr.dcproject.component.citizen.Citizen
|
import fr.dcproject.component.citizen.Citizen
|
||||||
import fr.dcproject.entity.FollowForUpdate
|
import fr.dcproject.entity.FollowForUpdate
|
||||||
import fr.dcproject.security.voter.FollowVoter.Action.*
|
import fr.dcproject.security.voter.FollowVoter
|
||||||
import fr.ktorVoter.assertCan
|
import fr.dcproject.voter.assert
|
||||||
import fr.ktorVoter.assertCanAll
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.locations.*
|
import io.ktor.locations.*
|
||||||
@@ -24,24 +24,24 @@ object FollowArticlePaths {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
fun Route.followArticle(repo: FollowArticleRepository) {
|
fun Route.followArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||||
post<FollowArticlePaths.ArticleFollowRequest> {
|
post<FollowArticlePaths.ArticleFollowRequest> {
|
||||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||||
assertCan(CREATE, follow)
|
voter.assert { canCreate(follow, citizenOrNull) }
|
||||||
repo.follow(follow)
|
repo.follow(follow)
|
||||||
call.respond(HttpStatusCode.Created)
|
call.respond(HttpStatusCode.Created)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete<FollowArticlePaths.ArticleFollowRequest> {
|
delete<FollowArticlePaths.ArticleFollowRequest> {
|
||||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||||
assertCan(DELETE, follow)
|
voter.assert { canDelete(follow, citizenOrNull) }
|
||||||
repo.unfollow(follow)
|
repo.unfollow(follow)
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
get<FollowArticlePaths.ArticleFollowRequest> {
|
get<FollowArticlePaths.ArticleFollowRequest> {
|
||||||
repo.findFollow(citizen, it.article)?.let { follow ->
|
repo.findFollow(citizen, it.article)?.let { follow ->
|
||||||
assertCan(VIEW, follow)
|
voter.assert { canView(follow, citizenOrNull) }
|
||||||
call.respond(follow)
|
call.respond(follow)
|
||||||
} ?: call.respond(HttpStatusCode.NoContent)
|
} ?: call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ fun Route.followArticle(repo: FollowArticleRepository) {
|
|||||||
get<FollowArticlePaths.CitizenFollowArticleRequest> {
|
get<FollowArticlePaths.CitizenFollowArticleRequest> {
|
||||||
val follows = repo.findByCitizen(it.citizen)
|
val follows = repo.findByCitizen(it.citizen)
|
||||||
if (follows.result.isNotEmpty()) {
|
if (follows.result.isNotEmpty()) {
|
||||||
assertCanAll(VIEW, follows.result)
|
voter.assert { canView(follows.result, citizenOrNull) }
|
||||||
}
|
}
|
||||||
call.respond(follows)
|
call.respond(follows)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package fr.dcproject.routes
|
package fr.dcproject.routes
|
||||||
|
|
||||||
import fr.dcproject.component.auth.citizen
|
import fr.dcproject.component.auth.citizen
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
import fr.dcproject.component.citizen.CitizenRef
|
import fr.dcproject.component.citizen.CitizenRef
|
||||||
import fr.dcproject.entity.ConstitutionRef
|
import fr.dcproject.entity.ConstitutionRef
|
||||||
import fr.dcproject.entity.FollowForUpdate
|
import fr.dcproject.entity.FollowForUpdate
|
||||||
import fr.dcproject.security.voter.FollowVoter.Action.*
|
import fr.dcproject.security.voter.FollowVoter
|
||||||
import fr.ktorVoter.assertCan
|
import fr.dcproject.voter.assert
|
||||||
import fr.ktorVoter.assertCanAll
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.locations.*
|
import io.ktor.locations.*
|
||||||
@@ -24,31 +24,31 @@ object FollowConstitutionPaths {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
fun Route.followConstitution(repo: FollowConstitutionRepository) {
|
fun Route.followConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||||
post<FollowConstitutionPaths.ConstitutionFollowRequest> {
|
post<FollowConstitutionPaths.ConstitutionFollowRequest> {
|
||||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||||
assertCan(CREATE, follow)
|
voter.assert { canCreate(follow, citizenOrNull) }
|
||||||
repo.follow(follow)
|
repo.follow(follow)
|
||||||
call.respond(HttpStatusCode.Created)
|
call.respond(HttpStatusCode.Created)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete<FollowConstitutionPaths.ConstitutionFollowRequest> {
|
delete<FollowConstitutionPaths.ConstitutionFollowRequest> {
|
||||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||||
assertCan(DELETE, follow)
|
voter.assert { canDelete(follow, citizenOrNull) }
|
||||||
repo.unfollow(follow)
|
repo.unfollow(follow)
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
get<FollowConstitutionPaths.ConstitutionFollowRequest> {
|
get<FollowConstitutionPaths.ConstitutionFollowRequest> {
|
||||||
repo.findFollow(citizen, it.constitution)?.let { follow ->
|
repo.findFollow(citizen, it.constitution)?.let { follow ->
|
||||||
assertCan(VIEW, follow)
|
voter.assert { canView(follow, citizenOrNull) }
|
||||||
call.respond(follow)
|
call.respond(follow)
|
||||||
} ?: call.respond(HttpStatusCode.NotFound)
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
get<FollowConstitutionPaths.CitizenFollowConstitutionRequest> {
|
get<FollowConstitutionPaths.CitizenFollowConstitutionRequest> {
|
||||||
val follows = repo.findByCitizen(it.citizen)
|
val follows = repo.findByCitizen(it.citizen)
|
||||||
assertCanAll(VIEW, follows.result)
|
voter.assert { canView(follows.result, citizenOrNull) }
|
||||||
call.respond(follows)
|
call.respond(follows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,26 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.component.auth.citizenOrNull
|
|
||||||
import fr.dcproject.component.citizen.CitizenI
|
import fr.dcproject.component.citizen.CitizenI
|
||||||
import fr.dcproject.entity.FollowI
|
import fr.dcproject.entity.FollowI
|
||||||
import fr.dcproject.voter.NoSubjectDefinedException
|
import fr.dcproject.voter.Voter
|
||||||
import fr.ktorVoter.*
|
import fr.dcproject.voter.VoterResponse
|
||||||
import io.ktor.application.*
|
|
||||||
import fr.dcproject.entity.Follow as FollowEntity
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
|
|
||||||
class FollowVoter : Voter<ApplicationCall> {
|
class FollowVoter : Voter() {
|
||||||
enum class Action : ActionI {
|
fun canCreate(subject: FollowI, citizen: CitizenI?): VoterResponse {
|
||||||
CREATE,
|
|
||||||
DELETE,
|
|
||||||
VIEW
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
return if (citizen == null) denied("You must be connected to follow", "follow.create.notConnected")
|
||||||
else granted()
|
else granted()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.DELETE) {
|
fun canDelete(subject: FollowI, citizen: CitizenI?): VoterResponse {
|
||||||
return if (citizen == null) denied("You must be connected to unfollow", "follow.delete.notConnected")
|
return if (citizen == null) denied("You must be connected to unfollow", "follow.delete.notConnected")
|
||||||
else granted()
|
else granted()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.VIEW) {
|
fun <S : FollowEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse =
|
||||||
if (subject is FollowEntity<*>) {
|
canAll(subjects) { canView(it, citizen) }
|
||||||
return voteView(citizen, subject)
|
|
||||||
}
|
|
||||||
throw NoSubjectDefinedException(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
return abstain()
|
fun canView(subject: FollowEntity<*>, citizen: CitizenI?): VoterResponse {
|
||||||
}
|
|
||||||
|
|
||||||
private fun voteView(citizen: CitizenI?, subject: FollowEntity<*>): VoterResponseI {
|
|
||||||
return if ((citizen != null && subject.createdBy.id == citizen.id) || !subject.createdBy.followAnonymous) granted()
|
return if ((citizen != null && subject.createdBy.id == citizen.id) || !subject.createdBy.followAnonymous) granted()
|
||||||
else denied("You cannot view an anonymous follow", "follow.view.anonymous")
|
else denied("You cannot view an anonymous follow", "follow.view.anonymous")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,28 +3,21 @@ package unit.voter
|
|||||||
import fr.dcproject.component.article.ArticleForView
|
import fr.dcproject.component.article.ArticleForView
|
||||||
import fr.dcproject.component.auth.User
|
import fr.dcproject.component.auth.User
|
||||||
import fr.dcproject.component.auth.UserI
|
import fr.dcproject.component.auth.UserI
|
||||||
import fr.dcproject.component.auth.citizenOrNull
|
|
||||||
import fr.dcproject.component.citizen.Citizen
|
import fr.dcproject.component.citizen.Citizen
|
||||||
import fr.dcproject.component.citizen.CitizenBasic
|
import fr.dcproject.component.citizen.CitizenBasic
|
||||||
import fr.dcproject.component.citizen.CitizenCart
|
import fr.dcproject.component.citizen.CitizenCart
|
||||||
import fr.dcproject.component.citizen.CitizenI
|
import fr.dcproject.component.citizen.CitizenI
|
||||||
import fr.dcproject.entity.Follow
|
import fr.dcproject.entity.Follow
|
||||||
import fr.dcproject.security.voter.FollowVoter
|
import fr.dcproject.security.voter.FollowVoter
|
||||||
import fr.dcproject.voter.NoSubjectDefinedException
|
import fr.dcproject.voter.Vote.DENIED
|
||||||
import fr.ktorVoter.ActionI
|
import fr.dcproject.voter.Vote.GRANTED
|
||||||
import fr.ktorVoter.Vote
|
|
||||||
import fr.ktorVoter.can
|
|
||||||
import fr.ktorVoter.canAll
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
import org.joda.time.DateTime
|
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 org.junit.jupiter.api.assertThrows
|
|
||||||
import org.junit.jupiter.api.parallel.Execution
|
import org.junit.jupiter.api.parallel.Execution
|
||||||
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
|
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -109,88 +102,58 @@ internal class FollowVoterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `support follow`(): Unit = FollowVoter().run {
|
fun `can be view the follow`() {
|
||||||
val p = object : ActionI {}
|
FollowVoter()
|
||||||
mockk<ApplicationCall> {
|
.canView(follow1, tesla2)
|
||||||
every { citizenOrNull } returns tesla2
|
.vote `should be` GRANTED
|
||||||
}.let {
|
|
||||||
this(FollowVoter.Action.VIEW, it, follow1).vote `should be` Vote.GRANTED
|
|
||||||
assertThrows<NoSubjectDefinedException> {
|
|
||||||
this(FollowVoter.Action.VIEW, it, article1)
|
|
||||||
}
|
|
||||||
this(p, it, follow1).vote `should be` Vote.ABSTAIN
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can be view the follow`(): Unit = listOf(FollowVoter()).run {
|
fun `can be view the follow list`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns tesla2
|
.canView(listOf(follow1), tesla2)
|
||||||
}.let {
|
.vote `should be` GRANTED
|
||||||
can(FollowVoter.Action.VIEW, it, follow1) `should be` true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can be view the follow list`(): Unit = listOf(FollowVoter()).run {
|
fun `can be view your anonymous follow`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns tesla2
|
.canView(followAnon, einstein3)
|
||||||
}.let {
|
.vote `should be` GRANTED
|
||||||
canAll(FollowVoter.Action.VIEW, it, listOf(follow1)) `should be` true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can be view your anonymous follow`(): Unit = listOf(FollowVoter()).run {
|
fun `can not be view the anonymous follow of other`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns einstein3
|
.canView(followAnon, tesla2)
|
||||||
}.let {
|
.vote `should be` DENIED
|
||||||
can(FollowVoter.Action.VIEW, it, followAnon) `should be` true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can not be view the anonymous follow of other`(): Unit = listOf(FollowVoter()).run {
|
fun `can be follow article`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns tesla2
|
.canCreate(follow1, tesla2)
|
||||||
}.let {
|
.vote `should be` GRANTED
|
||||||
can(FollowVoter.Action.VIEW, it, followAnon) `should be` false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can be follow article`(): Unit = listOf(FollowVoter()).run {
|
fun `can not be follow article if not connected`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns tesla2
|
.canCreate(follow1, null)
|
||||||
}.let {
|
.vote `should be` DENIED
|
||||||
can(FollowVoter.Action.CREATE, it, follow1) `should be` true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can not be follow article if not connected`(): Unit = listOf(FollowVoter()).run {
|
fun `can be unfollow article`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns null
|
.canDelete(follow1, tesla2)
|
||||||
}.let {
|
.vote `should be` GRANTED
|
||||||
can(FollowVoter.Action.CREATE, it, follow1) `should be` false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can be unfollow article`(): Unit = listOf(FollowVoter()).run {
|
fun `can not be unfollow article if not connected`() {
|
||||||
mockk<ApplicationCall> {
|
FollowVoter()
|
||||||
every { citizenOrNull } returns tesla2
|
.canDelete(follow1, null)
|
||||||
}.let {
|
.vote `should be` DENIED
|
||||||
can(FollowVoter.Action.DELETE, it, follow1) `should be` true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `can not be unfollow article if not connected`(): Unit = listOf(FollowVoter()).run {
|
|
||||||
mockk<ApplicationCall> {
|
|
||||||
every { citizenOrNull } returns null
|
|
||||||
}.let {
|
|
||||||
can(FollowVoter.Action.DELETE, it, follow1) `should be` false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user