diff --git a/src/main/kotlin/fr/dcproject/Application.kt b/src/main/kotlin/fr/dcproject/Application.kt index 0d1fec4..cbaa369 100644 --- a/src/main/kotlin/fr/dcproject/Application.kt +++ b/src/main/kotlin/fr/dcproject/Application.kt @@ -109,7 +109,8 @@ fun Application.module(env: Env = PROD) { ConstitutionVoter(), CitizenVoter(), CommentVoter(), - VoteVoter() + VoteVoter(), + FollowVoter() ) } diff --git a/src/main/kotlin/fr/dcproject/entity/Citizen.kt b/src/main/kotlin/fr/dcproject/entity/Citizen.kt index 8b17132..a5adc95 100644 --- a/src/main/kotlin/fr/dcproject/entity/Citizen.kt +++ b/src/main/kotlin/fr/dcproject/entity/Citizen.kt @@ -9,8 +9,8 @@ class Citizen( var name: Name?, var birthday: DateTime?, var userId: UUID? = null, - var voteanonymous: Boolean? = null, - var followanonymous: Boolean? = null, + var voteAnonymous: Boolean = true, + var followAnonymous: Boolean = true, var user: User? ) : UuidEntity(id), EntityCreatedAt by EntityCreatedAtImp(), diff --git a/src/main/kotlin/fr/dcproject/repository/Vote.kt b/src/main/kotlin/fr/dcproject/repository/Vote.kt index 4518b60..0cd241f 100644 --- a/src/main/kotlin/fr/dcproject/repository/Vote.kt +++ b/src/main/kotlin/fr/dcproject/repository/Vote.kt @@ -14,7 +14,7 @@ open class Vote (override var requester: Requester): RepositoryI< fun vote(vote: VoteEntity) { val reference = vote.target::class.simpleName!!.toLowerCase() val author = vote.createdBy ?: error("vote must be contain an author") - val anonymous = author.voteanonymous + val anonymous = author.voteAnonymous requester .getFunction("vote") .sendQuery( diff --git a/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt b/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt index 4885583..3aed049 100644 --- a/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt +++ b/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt @@ -2,6 +2,8 @@ package fr.dcproject.routes import fr.dcproject.citizen import fr.dcproject.entity.Citizen +import fr.dcproject.security.voter.FollowVoter.Action.* +import fr.dcproject.security.voter.assertCan import io.ktor.application.call import io.ktor.http.HttpStatusCode import io.ktor.locations.* @@ -21,24 +23,21 @@ object FollowArticlePaths { fun Route.followArticle(repo: FollowArticleRepository) { post { val follow = FollowEntity(target = it.article, createdBy = this.citizen) - // TODO create voter -// assertCan(FollowVoter.Action.CREATE, follow) + assertCan(CREATE, follow) repo.follow(follow) call.respond(HttpStatusCode.Created) } delete { val follow = FollowEntity(target = it.article, createdBy = this.citizen) - // TODO create voter -// assertCan(FollowVoter.Action.DELETE, follow) + assertCan(DELETE, follow) repo.unfollow(follow) call.respond(HttpStatusCode.NoContent) } get { val follows = repo.findByCitizen(it.citizen) - // TODO add security -// assertCan(FollowVoter.Action.VIEW, follows) + assertCan(VIEW, follows.result) call.respond(follows) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt b/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt index 82a04ca..68bd7d0 100644 --- a/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt +++ b/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt @@ -2,6 +2,8 @@ package fr.dcproject.routes import fr.dcproject.citizen import fr.dcproject.entity.Citizen +import fr.dcproject.security.voter.FollowVoter.Action.* +import fr.dcproject.security.voter.assertCan import io.ktor.application.call import io.ktor.http.HttpStatusCode import io.ktor.locations.* @@ -21,24 +23,21 @@ object FollowConstitutionPaths { fun Route.followConstitution(repo: FollowConstitutionRepository) { post { val follow = FollowEntity(target = it.constitution, createdBy = this.citizen) -// TODO create voter -// assertCan(FollowVoter.Action.CREATE, follow) + assertCan(CREATE, follow) repo.follow(follow) call.respond(HttpStatusCode.Created) } delete { val follow = FollowEntity(target = it.constitution, createdBy = this.citizen) -// TODO create voter -// assertCan(FollowVoter.Action.DELETE, follow) + assertCan(DELETE, follow) repo.unfollow(follow) call.respond(HttpStatusCode.NoContent) } get { val follows = repo.findByCitizen(it.citizen) -// TODO create voter -// assertCan(FollowVoter.Action.VIEW, follows) + assertCan(VIEW, follows.result) call.respond(follows) } } diff --git a/src/main/kotlin/fr/dcproject/security/voter/FollowVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/FollowVoter.kt new file mode 100644 index 0000000..519c81a --- /dev/null +++ b/src/main/kotlin/fr/dcproject/security/voter/FollowVoter.kt @@ -0,0 +1,53 @@ +package fr.dcproject.security.voter + +import io.ktor.application.ApplicationCall +import fr.dcproject.entity.Follow as FollowEntity +import fr.dcproject.entity.User as UserEntity + +class FollowVoter: Voter { + enum class Action: ActionI { + CREATE, + DELETE, + VIEW + } + + override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { + return (action is Action) && + (subject is List<*> || subject is FollowEntity<*>?) + } + + override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { + val user = call.user + if (action == Action.CREATE) { + return if (user != null) Vote.GRANTED + else Vote.DENIED + } + + if (action == Action.DELETE) { + return if (user != null) Vote.GRANTED + else Vote.DENIED + } + + if (action == Action.VIEW) { + if (subject is FollowEntity<*>) { + return voteView(user, subject) + } + if (subject is List<*>) { + subject.forEach { + if (it !is FollowEntity<*> || voteView(user, it) == Vote.DENIED) { + return Vote.DENIED + } + } + return Vote.GRANTED + } + return Vote.DENIED + } + + return Vote.ABSTAIN + } + + private fun voteView(user: UserEntity?, subject: FollowEntity<*>): Vote { + return if ((user != null && subject.createdBy?.user?.id == user.id) || subject.createdBy?.followAnonymous == false) Vote.GRANTED + else Vote.DENIED + } +} diff --git a/src/test/kotlin/feature/KtorServerAuthSteps.kt b/src/test/kotlin/feature/KtorServerAuthSteps.kt index 35e13d4..6c0e7e8 100644 --- a/src/test/kotlin/feature/KtorServerAuthSteps.kt +++ b/src/test/kotlin/feature/KtorServerAuthSteps.kt @@ -78,7 +78,9 @@ class KtorServerAuthSteps: En, KoinTest { id = UUID.fromString(id), name = Citizen.Name(firstName, lastName), birthday = DateTime.now(), - user = user + user = user, + followAnonymous = false, + voteAnonymous = false ) try { diff --git a/src/test/resources/feature/follow.feature b/src/test/resources/feature/follow.feature index 2afade1..20407fc 100644 --- a/src/test/resources/feature/follow.feature +++ b/src/test/resources/feature/follow.feature @@ -7,8 +7,8 @@ Feature: follow Article and Constitution Then the response status code should be 201 Scenario: The route for get follows of articles must response a 200 and return objects - Given I have citizen John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" - When I send a GET request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/follows/articles" + Given I have citizen John Smith with id "e3c0b08c-11be-418e-95e0-8596b4402feb" + When I send a GET request to "/citizens/e3c0b08c-11be-418e-95e0-8596b4402feb/follows/articles" Then the response status code should be 200 And the response should contain object: | current_page | 1 | @@ -26,8 +26,8 @@ Feature: follow Article and Constitution Then the response status code should be 201 Scenario: The route for get follows of constitutions must response a 200 and return objects - Given I have citizen John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" - When I send a GET request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/follows/constitutions" + Given I have citizen John Smith with id "e3c0b08c-11be-418e-95e0-8596b4402feb" + When I send a GET request to "/citizens/e3c0b08c-11be-418e-95e0-8596b4402feb/follows/constitutions" Then the response status code should be 200 And the response should contain object: | current_page | 1 |