diff --git a/src/main/kotlin/fr/dcproject/entity/Citizen.kt b/src/main/kotlin/fr/dcproject/entity/Citizen.kt index 20e86b5..8b17132 100644 --- a/src/main/kotlin/fr/dcproject/entity/Citizen.kt +++ b/src/main/kotlin/fr/dcproject/entity/Citizen.kt @@ -1,12 +1,9 @@ package fr.dcproject.entity -import fr.postgresjson.entity.EntityCreatedAt -import fr.postgresjson.entity.EntityCreatedAtImp -import fr.postgresjson.entity.UuidEntity +import fr.postgresjson.entity.* import org.joda.time.DateTime import java.util.* - class Citizen( id: UUID = UUID.randomUUID(), var name: Name?, @@ -16,7 +13,8 @@ class Citizen( var followanonymous: Boolean? = null, var user: User? ) : UuidEntity(id), - EntityCreatedAt by EntityCreatedAtImp() { + EntityCreatedAt by EntityCreatedAtImp(), + EntityDeletedAt by EntityDeletedAtImp() { data class Name( var firstName: String?, var lastName: String?, diff --git a/src/main/kotlin/fr/dcproject/entity/Comment.kt b/src/main/kotlin/fr/dcproject/entity/Comment.kt index 900df6e..4f1551e 100644 --- a/src/main/kotlin/fr/dcproject/entity/Comment.kt +++ b/src/main/kotlin/fr/dcproject/entity/Comment.kt @@ -1,8 +1,6 @@ package fr.dcproject.entity -import fr.postgresjson.entity.EntityUpdatedAt -import fr.postgresjson.entity.EntityUpdatedAtImp -import fr.postgresjson.entity.UuidEntity + import fr.postgresjson.entity.* import java.util.* open class Comment ( @@ -14,4 +12,5 @@ open class Comment ( var parent: Comment? = null, var parentsIds: List? = null ): Extra(id, createdBy), - EntityUpdatedAt by EntityUpdatedAtImp() + EntityUpdatedAt by EntityUpdatedAtImp(), + EntityDeletedAt by EntityDeletedAtImp() diff --git a/src/main/kotlin/fr/dcproject/routes/Article.kt b/src/main/kotlin/fr/dcproject/routes/Article.kt index 8514c81..bcd84ff 100644 --- a/src/main/kotlin/fr/dcproject/routes/Article.kt +++ b/src/main/kotlin/fr/dcproject/routes/Article.kt @@ -16,7 +16,6 @@ import io.ktor.routing.Route import fr.dcproject.entity.Article as ArticleEntity import fr.dcproject.repository.Article as ArticleRepository - @KtorExperimentalLocationsAPI object ArticlesPaths { @Location("/articles") class ArticlesRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: RepositoryI.Direction? = null, val search: String? = null) { @@ -30,9 +29,8 @@ object ArticlesPaths { @KtorExperimentalLocationsAPI fun Route.article(repo: ArticleRepository) { get { - assertCan(VIEW) - val articles = repo.find(it.page, it.limit, it.sort, it.direction, it.search) + assertCan(VIEW, articles.result) call.respond(articles) } @@ -43,11 +41,11 @@ fun Route.article(repo: ArticleRepository) { } post { - assertCan(CREATE) - val article = call.receive() article.createdBy = citizen + assertCan(CREATE, article) + repo.upsert(article) call.respond(article) diff --git a/src/main/kotlin/fr/dcproject/routes/Citizen.kt b/src/main/kotlin/fr/dcproject/routes/Citizen.kt index 4355356..348ea7c 100644 --- a/src/main/kotlin/fr/dcproject/routes/Citizen.kt +++ b/src/main/kotlin/fr/dcproject/routes/Citizen.kt @@ -26,9 +26,8 @@ object CitizenPaths { @KtorExperimentalLocationsAPI fun Route.citizen(repo: CitizenRepository) { get { - assertCan(VIEW) - val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search) + assertCan(VIEW, citizens.result) call.respond(citizens) } diff --git a/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt b/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt index 5a6c1da..d9aa456 100644 --- a/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt +++ b/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt @@ -27,10 +27,8 @@ object CommentArticlePaths { @KtorExperimentalLocationsAPI fun Route.commentArticle(repo: CommentArticleRepository) { get { - assertCan(VIEW, it.article) - val comment = repo.findByTarget(it.article) - + assertCan(VIEW, comment.result) call.respond(HttpStatusCode.OK, comment) } @@ -50,6 +48,7 @@ fun Route.commentArticle(repo: CommentArticleRepository) { get { val comments = repo.findByCitizen(it.citizen) + assertCan(VIEW, comments.result) call.respond(comments) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt b/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt index 12449fd..e53a716 100644 --- a/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt +++ b/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt @@ -27,22 +27,19 @@ object CommentConstitutionPaths { @KtorExperimentalLocationsAPI fun Route.commentConstitution(repo: CommentConstitutionRepository) { get { - assertCan(VIEW, it.constitution) - - val comment = repo.findByTarget(it.constitution) - - call.respond(HttpStatusCode.OK, comment) + val comments = repo.findByTarget(it.constitution) + assertCan(VIEW, comments.result) + call.respond(HttpStatusCode.OK, comments) } post { - assertCan(CREATE, it.constitution) - val content = call.receiveText() val comment = CommentEntity( target = it.constitution, createdBy = citizen, content = content ) + assertCan(CREATE, comment) repo.comment(comment) call.respond(HttpStatusCode.Created, comment) @@ -50,6 +47,7 @@ fun Route.commentConstitution(repo: CommentConstitutionRepository) { get { val comments = repo.findByCitizen(it.citizen) + assertCan(VIEW, comments.result) call.respond(comments) } } \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/routes/Constitution.kt b/src/main/kotlin/fr/dcproject/routes/Constitution.kt index d9ca780..2f9ef7d 100644 --- a/src/main/kotlin/fr/dcproject/routes/Constitution.kt +++ b/src/main/kotlin/fr/dcproject/routes/Constitution.kt @@ -1,6 +1,9 @@ package fr.dcproject.routes import fr.dcproject.citizen +import fr.dcproject.security.voter.ConstitutionVoter.Action.CREATE +import fr.dcproject.security.voter.ConstitutionVoter.Action.VIEW +import fr.dcproject.security.voter.assertCan import fr.postgresjson.repository.RepositoryI import io.ktor.application.call import io.ktor.locations.KtorExperimentalLocationsAPI @@ -28,16 +31,19 @@ object ConstitutionPaths { fun Route.constitution(repo: ConstitutionRepository) { get { val constitutions = repo.find(it.page, it.limit, it.sort, it.direction, it.search) + assertCan(VIEW, constitutions.result) call.respond(constitutions) } get { + assertCan(VIEW, it.constitution) call.respond(it.constitution) } post { val constitution = call.receive() constitution.createdBy = citizen + assertCan(CREATE, constitution) repo.upsert(constitution) diff --git a/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt b/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt index 0bc6db5..4885583 100644 --- a/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt +++ b/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt @@ -20,17 +20,25 @@ object FollowArticlePaths { @KtorExperimentalLocationsAPI fun Route.followArticle(repo: FollowArticleRepository) { post { - repo.follow(FollowEntity(target = it.article, createdBy = this.citizen)) + val follow = FollowEntity(target = it.article, createdBy = this.citizen) + // TODO create voter +// assertCan(FollowVoter.Action.CREATE, follow) + repo.follow(follow) call.respond(HttpStatusCode.Created) } delete { - repo.unfollow(FollowEntity(target = it.article, createdBy = this.citizen)) + val follow = FollowEntity(target = it.article, createdBy = this.citizen) + // TODO create voter +// assertCan(FollowVoter.Action.DELETE, follow) + repo.unfollow(follow) call.respond(HttpStatusCode.NoContent) } get { val follows = repo.findByCitizen(it.citizen) + // TODO add security +// assertCan(FollowVoter.Action.VIEW, follows) 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 b4f2fe9..82a04ca 100644 --- a/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt +++ b/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt @@ -20,17 +20,25 @@ object FollowConstitutionPaths { @KtorExperimentalLocationsAPI fun Route.followConstitution(repo: FollowConstitutionRepository) { post { - repo.follow(FollowEntity(target = it.constitution, createdBy = this.citizen)) + val follow = FollowEntity(target = it.constitution, createdBy = this.citizen) +// TODO create voter +// assertCan(FollowVoter.Action.CREATE, follow) + repo.follow(follow) call.respond(HttpStatusCode.Created) } delete { - repo.unfollow(FollowEntity(target = it.constitution, createdBy = this.citizen)) + val follow = FollowEntity(target = it.constitution, createdBy = this.citizen) +// TODO create voter +// assertCan(FollowVoter.Action.DELETE, follow) + repo.unfollow(follow) call.respond(HttpStatusCode.NoContent) } get { val follows = repo.findByCitizen(it.citizen) +// TODO create voter +// assertCan(FollowVoter.Action.VIEW, follows) call.respond(follows) } } diff --git a/src/main/kotlin/fr/dcproject/security/voter/ArticleVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/ArticleVoter.kt index c29bb36..a93824a 100644 --- a/src/main/kotlin/fr/dcproject/security/voter/ArticleVoter.kt +++ b/src/main/kotlin/fr/dcproject/security/voter/ArticleVoter.kt @@ -3,6 +3,7 @@ package fr.dcproject.security.voter import fr.dcproject.entity.User import io.ktor.application.ApplicationCall import fr.dcproject.entity.Article as ArticleEntity +import fr.dcproject.entity.Comment as CommentEntity import fr.dcproject.entity.Vote as VoteEntity class ArticleVoter: Voter { @@ -15,7 +16,8 @@ class ArticleVoter: Voter { override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { return (action is Action || action is CommentVoter.Action || action is VoteVoter.Action) - && subject is ArticleEntity? + && + (subject is List<*> || subject is ArticleEntity? || subject is VoteEntity<*> || subject is CommentEntity<*>) } override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { @@ -25,7 +27,19 @@ class ArticleVoter: Voter { } if (action == Action.VIEW) { - return Vote.GRANTED + if (subject is ArticleEntity) { + return if (subject.isDeleted()) Vote.DENIED + else Vote.GRANTED + } + if (subject is List<*>) { + subject.forEach { + if (it !is ArticleEntity || it.isDeleted()) { + return Vote.DENIED + } + } + return Vote.GRANTED + } + return Vote.DENIED } if (action is CommentVoter.Action) return voteForComment(action) diff --git a/src/main/kotlin/fr/dcproject/security/voter/CitizenVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/CitizenVoter.kt index 7d2a264..4669a37 100644 --- a/src/main/kotlin/fr/dcproject/security/voter/CitizenVoter.kt +++ b/src/main/kotlin/fr/dcproject/security/voter/CitizenVoter.kt @@ -13,7 +13,9 @@ class CitizenVoter: Voter { } override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { - return action is Action && subject is Citizen? + return (action is Action) + && + (subject is List<*> || subject is Citizen?) } override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { @@ -22,8 +24,20 @@ class CitizenVoter: Voter { return Vote.GRANTED } - if (action == Action.VIEW && user != null) { - return Vote.GRANTED + if (action == Action.VIEW) { + if (subject is Citizen) { + return if (subject.isDeleted()) Vote.DENIED + else Vote.GRANTED + } + if (subject is List<*>) { + subject.forEach { + if (it !is Citizen || it.isDeleted()) { + return Vote.DENIED + } + } + return Vote.GRANTED + } + return Vote.DENIED } if (action == Action.DELETE) { diff --git a/src/main/kotlin/fr/dcproject/security/voter/CommentVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/CommentVoter.kt index 924bff6..f95a947 100644 --- a/src/main/kotlin/fr/dcproject/security/voter/CommentVoter.kt +++ b/src/main/kotlin/fr/dcproject/security/voter/CommentVoter.kt @@ -12,7 +12,8 @@ class CommentVoter: Voter { } override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { - return action is Action && subject is Comment<*>? + return (action is Action) && + (subject is Comment<*>? || subject is List<*>) } override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { @@ -22,7 +23,19 @@ class CommentVoter: Voter { } if (action == Action.VIEW) { - return Vote.GRANTED + if (subject is Comment<*>) { + return if (subject.isDeleted()) Vote.DENIED + else Vote.GRANTED + } + if (subject is List<*>) { + subject.forEach { + if (it !is Comment<*> || it.isDeleted()) { + return Vote.DENIED + } + } + return Vote.GRANTED + } + return Vote.DENIED } if (action == Action.UPDATE && user != null && subject is Comment<*> && user.id == subject.createdBy?.userId) { diff --git a/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt index e311eb4..b514e81 100644 --- a/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt +++ b/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt @@ -1,5 +1,6 @@ package fr.dcproject.security.voter +import fr.dcproject.entity.Comment import fr.dcproject.entity.User import io.ktor.application.ApplicationCall import fr.dcproject.entity.Constitution as ConstitutionEntity @@ -14,7 +15,9 @@ class ConstitutionVoter: Voter { } override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { - return (action is Action || action is CommentVoter.Action) && subject is ConstitutionEntity? + return (action is Action || action is CommentVoter.Action || action is VoteVoter.Action) + && + (subject is List<*> || subject is ConstitutionEntity? || subject is VoteEntity<*> || subject is Comment<*>) } override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { @@ -24,7 +27,19 @@ class ConstitutionVoter: Voter { } if (action == Action.VIEW) { - return Vote.GRANTED + if (subject is ConstitutionEntity) { + return if (subject.isDeleted()) Vote.DENIED + else Vote.GRANTED + } + if (subject is List<*>) { + subject.forEach { + if (it !is ConstitutionEntity || it.isDeleted()) { + return Vote.DENIED + } + } + return Vote.GRANTED + } + return Vote.DENIED } if (action == Action.DELETE && user is User && subject is ConstitutionEntity && subject.createdBy?.userId == user.id) { diff --git a/src/main/resources/sql/migrations/0000-init_schema.up.sql b/src/main/resources/sql/migrations/0000-init_schema.up.sql index 35028c3..de45e1b 100644 --- a/src/main/resources/sql/migrations/0000-init_schema.up.sql +++ b/src/main/resources/sql/migrations/0000-init_schema.up.sql @@ -236,6 +236,7 @@ create table comment "content" text not null check ( content != '' and length(content) < 4096), parent_id uuid references comment (id), parents_ids uuid[], + deleted_at timestamptz null, foreign key (created_by_id) references citizen (id), primary key (id) ) inherits (extra);