Refactoring of CommentVoter

This commit is contained in:
2021-01-14 23:38:59 +01:00
parent caadc2a969
commit 7c106f7cf8
13 changed files with 169 additions and 192 deletions

View File

@@ -16,6 +16,7 @@ class CommentForView<T : TargetI, C : CitizenRef>(
val childrenCount: Int? = null,
override val deletedAt: DateTime? = null
) : ExtraI<T, C>,
CommentWithParentI<T>,
CommentForUpdate<T, C>(id, createdBy, target, content, parent, deletedAt),
CommentWithTargetI<T>,
EntityCreatedBy<C> by EntityCreatedByImp(createdBy),
@@ -40,9 +41,10 @@ open class CommentForUpdate<T : TargetI, C : CitizenRef>(
override val createdBy: C,
override val target: T,
open var content: String,
open val parent: CommentParent<T>? = null,
override val parent: CommentParent<T>? = null,
override val deletedAt: DateTime? = null
) : CommentParent<T>(id, deletedAt, target),
CommentWithParentI<T>,
ExtraI<T, C>,
CommentWithTargetI<T>,
EntityCreatedAt by EntityCreatedAtImp(),
@@ -72,6 +74,10 @@ interface CommentParentI<T : TargetI> : CommentI, EntityDeletedAt, CommentWithTa
interface CommentWithTargetI<T : TargetI> : CommentI, TargetI, AsTarget<T>
interface CommentWithParentI<T : TargetI> {
val parent: CommentParent<T>?
}
open class CommentRef(id: UUID = UUID.randomUUID()) : CommentI, TargetRef(id)
interface CommentI : EntityI

View File

@@ -1,61 +1,41 @@
package fr.dcproject.component.comment.generic
import fr.dcproject.citizenOrNull
import fr.dcproject.voter.NoRuleDefinedException
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.*
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.AsTarget
import fr.dcproject.voter.Voter
import fr.dcproject.voter.VoterResponse
import fr.postgresjson.entity.EntityCreatedBy
import fr.postgresjson.entity.EntityDeletedAt
import io.ktor.application.*
class CommentVoter : Voter<ApplicationCall> {
enum class Action : ActionI {
CREATE,
UPDATE,
VIEW,
DELETE
class CommentVoter : Voter() {
fun <S> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse
where S : CommentI,
S : EntityDeletedAt = canAll(subjects) { canView(it, citizen) }
fun <S> canView(subject: S, citizen: CitizenI?): VoterResponse
where S : CommentI,
S : EntityDeletedAt = when {
subject.isDeleted() -> denied("Your cannot view a deleted comment", "comment.view.deleted")
else -> granted()
}
override fun invoke(action: Any, context: ApplicationCall, subject: Any?): VoterResponseI {
if (!(action is Action && subject is CommentI?)) return abstain()
fun <S, CR : CitizenI> canCreate(subject: S, citizen: CitizenI?): VoterResponse
where S : CommentI,
S : EntityCreatedBy<CR>,
S : CommentWithParentI<*>,
S : AsTarget<*> = when {
citizen == null -> denied("You must be connected to create user", "comment.create.notConnected")
subject.createdBy.id != citizen.id -> denied("You cannot create a comment with other user than yours", "comment.create.wrongUser")
subject.parent?.isDeleted() ?: false -> denied("You cannot create a comment on deleted parent", "comment.create.deletedParent")
subject.target.let { it is EntityDeletedAt && it.isDeleted() } -> denied("You cannot create a comment on deleted target", "comment.create.deletedTarget")
else -> granted()
}
val citizen = context.citizenOrNull
if (subject == null) {
throw NoSubjectDefinedException(action)
}
if (action == Action.CREATE) {
return when {
citizen == null -> denied("You must be connected to create user", "comment.create.notConnected")
subject !is CommentForUpdate<*, *> -> throw NoSubjectDefinedException(action)
subject.createdBy.id != citizen.id -> denied("You cannot create a comment with other user than yours", "comment.create.wrongUser")
subject.parent?.isDeleted() ?: false -> denied("You cannot create a comment on deleted parent", "comment.create.deletedParent")
subject.target.let { it is EntityDeletedAt && it.isDeleted() } -> denied("You cannot create a comment on deleted target", "comment.create.deletedTarget")
else -> granted()
}
}
if (action == Action.VIEW) {
return when {
subject !is CommentForView<*, *> -> throw NoSubjectDefinedException(action)
subject.isDeleted() -> denied("Your cannot view a deleted comment", "comment.view.deleted")
else -> granted()
}
}
if (action == Action.UPDATE) {
if (citizen == null) return denied("You must be connected to update comment", "comment.update.notConnected")
return when {
subject !is CommentForUpdate<*, *> -> throw NoSubjectDefinedException(action)
citizen.id == subject.createdBy.id -> granted()
else -> denied("You cannot update another user of yours", "comment.update.notYours")
}
}
if (action == Action.DELETE) {
return denied("A comment can never be deleted", "comment.deleted.never")
}
throw NoRuleDefinedException(action)
fun <S, CR : CitizenI> canUpdate(subject: S, citizen: CitizenI?): VoterResponse
where S : CommentI,
S : EntityCreatedBy<CR> = when {
citizen == null -> denied("You must be connected to update comment", "comment.update.notConnected")
citizen.id != subject.createdBy.id -> denied("You cannot update another user of yours", "comment.update.notYours")
else -> granted()
}
}

View File

@@ -1,11 +1,12 @@
package fr.dcproject.component.comment.generic.routes
import fr.dcproject.citizen
import fr.dcproject.citizenOrNull
import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter
import fr.ktorVoter.assertCan
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
@@ -23,7 +24,7 @@ class CreateCommentChildrenRequest(val comment: CommentRef) {
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.createCommentChildren(repo: CommentRepository) {
fun Route.createCommentChildren(repo: CommentRepository, voter: CommentVoter) {
post<CreateCommentChildrenRequest> {
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
val newComment = CommentForUpdate(
@@ -32,7 +33,7 @@ fun Route.createCommentChildren(repo: CommentRepository) {
parent = parent
)
assertCan(CommentVoter.Action.CREATE, newComment)
voter.assert { canCreate(newComment, citizenOrNull) }
repo.comment(newComment)
call.respond(HttpStatusCode.Created, newComment)

View File

@@ -1,10 +1,12 @@
package fr.dcproject.component.comment.generic.routes
import fr.dcproject.citizenOrNull
import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter
import fr.ktorVoter.assertCan
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.*
@@ -18,10 +20,10 @@ class EditCommentRequest(val comment: CommentRef)
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.editComment(repo: CommentRepository) {
fun Route.editComment(repo: CommentRepository, voter: CommentVoter) {
put<EditCommentRequest> {
val comment = repo.findById(it.comment.id)!!
assertCan(CommentVoter.Action.UPDATE, comment)
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
voter.assert { canUpdate(comment, citizenOrNull) }
comment.content = call.receiveText()
repo.edit(comment)

View File

@@ -1,8 +1,9 @@
package fr.dcproject.component.comment.generic.routes
import fr.dcproject.citizenOrNull
import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter
import fr.ktorVoter.assertCanAll
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
@@ -25,7 +26,7 @@ class CommentChildrenRequest(
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.getChildrenComments(repo: CommentRepository) {
fun Route.getChildrenComments(repo: CommentRepository, voter: CommentVoter) {
get<CommentChildrenRequest> {
val comments =
repo.findByParent(
@@ -34,7 +35,7 @@ fun Route.getChildrenComments(repo: CommentRepository) {
it.limit
)
assertCanAll(CommentVoter.Action.VIEW, comments.result)
voter.assert { canView(comments.result, citizenOrNull) }
call.respond(HttpStatusCode.OK, comments)
}

View File

@@ -1,9 +1,10 @@
package fr.dcproject.component.comment.generic.routes
import fr.dcproject.citizenOrNull
import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter
import fr.ktorVoter.assertCan
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
@@ -18,10 +19,10 @@ class CommentRequest(val comment: CommentRef)
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.getOneComment(repo: CommentRepository) {
fun Route.getOneComment(repo: CommentRepository, voter: CommentVoter) {
get<CommentRequest> {
val comment = repo.findById(it.comment.id) ?: NotFoundException("Comment ${it.comment.id} not found")
assertCan(CommentVoter.Action.VIEW, comment)
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
voter.assert { canView(comment, citizenOrNull) }
call.respond(HttpStatusCode.OK, comment)
}