Rename Voter to AccessControl

This commit is contained in:
2021-01-22 22:07:25 +01:00
parent c1b8b508ac
commit 49a03a57cb
63 changed files with 462 additions and 462 deletions

View File

@@ -28,7 +28,7 @@ import fr.dcproject.routes.commentConstitution
import fr.dcproject.routes.constitution import fr.dcproject.routes.constitution
import fr.dcproject.routes.definition import fr.dcproject.routes.definition
import fr.dcproject.routes.notificationArticle import fr.dcproject.routes.notificationArticle
import fr.dcproject.voter.VoterDeniedException import fr.dcproject.security.AccessDeniedException
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.call import io.ktor.application.call
@@ -166,7 +166,7 @@ fun Application.module(env: Env = PROD) {
exception<NotFoundException> { e -> exception<NotFoundException> { e ->
call.respond(HttpStatusCode.NotFound, e.message!!) call.respond(HttpStatusCode.NotFound, e.message!!)
} }
exception<VoterDeniedException> { exception<AccessDeniedException> {
if (call.user == null) call.respond(HttpStatusCode.Unauthorized) if (call.user == null) call.respond(HttpStatusCode.Unauthorized)
else call.respond(HttpStatusCode.Forbidden) else call.respond(HttpStatusCode.Forbidden)
} }

View File

@@ -8,31 +8,31 @@ import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.joda.JodaModule import com.fasterxml.jackson.datatype.joda.JodaModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.rabbitmq.client.ConnectionFactory import com.rabbitmq.client.ConnectionFactory
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleViewManager import fr.dcproject.component.article.ArticleViewManager
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.auth.PasswordlessAuth import fr.dcproject.component.auth.PasswordlessAuth
import fr.dcproject.component.auth.UserRepository import fr.dcproject.component.auth.UserRepository
import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.citizen.CitizenVoter
import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.OpinionChoiceAccessControl
import fr.dcproject.component.opinion.OpinionChoiceRepository import fr.dcproject.component.opinion.OpinionChoiceRepository
import fr.dcproject.component.opinion.OpinionChoiceVoter import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.opinion.OpinionVoter
import fr.dcproject.component.vote.VoteArticleRepository import fr.dcproject.component.vote.VoteArticleRepository
import fr.dcproject.component.vote.VoteCommentRepository import fr.dcproject.component.vote.VoteCommentRepository
import fr.dcproject.component.vote.VoteConstitutionRepository import fr.dcproject.component.vote.VoteConstitutionRepository
import fr.dcproject.component.vote.VoteRepository import fr.dcproject.component.vote.VoteRepository
import fr.dcproject.component.vote.VoteVoter import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter
import fr.dcproject.event.publisher.Publisher import fr.dcproject.event.publisher.Publisher
import fr.dcproject.messages.Mailer 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.ConstitutionAccessControl
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
@@ -125,16 +125,16 @@ val KoinModule = module {
single { OpinionArticleRepository(get()) } single { OpinionArticleRepository(get()) }
single { WorkgroupRepository(get()) } single { WorkgroupRepository(get()) }
// Voters // AccessControl
single { ArticleVoter(get()) } single { ArticleAccessControl(get()) }
single { CitizenVoter() } single { CitizenAccessControl() }
single { CommentVoter() } single { CommentAccessControl() }
single { WorkgroupVoter() } single { WorkgroupAccessControl() }
single { ConstitutionVoter() } single { ConstitutionAccessControl() }
single { VoteVoter() } single { VoteAccessControl() }
single { FollowVoter() } single { FollowAccessControl() }
single { OpinionVoter() } single { OpinionAccessControl() }
single { OpinionChoiceVoter() } single { OpinionChoiceAccessControl() }
// Elasticsearch Client // Elasticsearch Client
single<RestClient> { single<RestClient> {

View File

@@ -3,20 +3,20 @@ package fr.dcproject.component.article
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.CreatedBy import fr.dcproject.entity.CreatedBy
import fr.dcproject.entity.VersionableRef import fr.dcproject.entity.VersionableRef
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
class ArticleVoter(private val articleRepo: ArticleRepository) : Voter() { class ArticleAccessControl(private val articleRepo: ArticleRepository) : AccessControl() {
fun <S : ArticleAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse = fun <S : ArticleAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun <S : ArticleAuthI<*>> canView(subject: S, citizen: CitizenI?): VoterResponse { fun <S : ArticleAuthI<*>> canView(subject: S, citizen: CitizenI?): AccessResponse {
return if (subject.isDeleted()) denied("Article is deleted", "article.deleted") return if (subject.isDeleted()) denied("Article is deleted", "article.deleted")
else if (subject.draft && (citizen == null || subject.createdBy.id != citizen.id)) denied("Article is draft, but it's not yours", "article.draft.not.yours") else if (subject.draft && (citizen == null || subject.createdBy.id != citizen.id)) denied("Article is draft, but it's not yours", "article.draft.not.yours")
else granted() else granted()
} }
fun <S : CreatedBy<*>> canDelete(subject: S, citizen: CitizenI?): VoterResponse { fun <S : CreatedBy<*>> canDelete(subject: S, citizen: CitizenI?): AccessResponse {
if (citizen == null) return denied("You must be connected to create article", "article.create.notConnected") if (citizen == null) return denied("You must be connected to create article", "article.create.notConnected")
return if (subject.createdBy.id == citizen.id) { return if (subject.createdBy.id == citizen.id) {
granted() granted()
@@ -25,7 +25,7 @@ class ArticleVoter(private val articleRepo: ArticleRepository) : Voter() {
} }
} }
fun <S> canUpsert(subject: S, citizen: CitizenI?): VoterResponse fun <S> canUpsert(subject: S, citizen: CitizenI?): AccessResponse
where S : ArticleI, where S : ArticleI,
S : CreatedBy<*>, S : CreatedBy<*>,
S : VersionableRef { S : VersionableRef {

View File

@@ -1,10 +1,10 @@
package fr.dcproject.component.article.routes package fr.dcproject.component.article.routes
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -31,10 +31,10 @@ object FindArticleVersions {
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) = private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
findVersionsByVersionId(request.page, request.limit, request.article.versionId) findVersionsByVersionId(request.page, request.limit, request.article.versionId)
fun Route.findArticleVersions(repo: ArticleRepository, voter: ArticleVoter) { fun Route.findArticleVersions(repo: ArticleRepository, ac: ArticleAccessControl) {
get<ArticleVersionsRequest> { get<ArticleVersionsRequest> {
repo.findVersions(it) repo.findVersions(it)
.apply { voter.assert { canView(it.article, citizenOrNull) } } .apply { ac.assert { canView(it.article, citizenOrNull) } }
.let { call.respond(it) } .let { call.respond(it) }
} }
} }

View File

@@ -1,12 +1,12 @@
package fr.dcproject.component.article.routes package fr.dcproject.component.article.routes
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleForListing import fr.dcproject.component.article.ArticleForListing
import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI import fr.dcproject.routes.PaginatedRequestI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Paginated
import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI
import io.ktor.application.call import io.ktor.application.call
@@ -40,10 +40,10 @@ object FindArticles {
) )
} }
fun Route.findArticles(repo: ArticleRepository, voter: ArticleVoter) { fun Route.findArticles(repo: ArticleRepository, ac: ArticleAccessControl) {
get<ArticlesRequest> { get<ArticlesRequest> {
repo.findArticles(it) repo.findArticles(it)
.apply { voter.assert { canView(result, citizenOrNull) } } .apply { ac.assert { canView(result, citizenOrNull) } }
.let { call.respond(it) } .let { call.respond(it) }
} }
} }

View File

@@ -1,9 +1,9 @@
package fr.dcproject.component.article.routes package fr.dcproject.component.article.routes
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleViewManager import fr.dcproject.component.article.ArticleViewManager
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.article.routes.GetOneArticle.ArticleRequest.Output import fr.dcproject.component.article.routes.GetOneArticle.ArticleRequest.Output
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.opinion.dto.Opinionable import fr.dcproject.component.opinion.dto.Opinionable
@@ -11,7 +11,7 @@ import fr.dcproject.component.vote.dto.Votable
import fr.dcproject.dto.CreatedAt import fr.dcproject.dto.CreatedAt
import fr.dcproject.dto.Versionable import fr.dcproject.dto.Versionable
import fr.dcproject.dto.Viewable import fr.dcproject.dto.Viewable
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.features.NotFoundException import io.ktor.features.NotFoundException
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -53,9 +53,9 @@ object GetOneArticle {
} }
} }
fun Route.getOneArticle(viewManager: ArticleViewManager, voter: ArticleVoter) { fun Route.getOneArticle(viewManager: ArticleViewManager, ac: ArticleAccessControl) {
get<ArticleRequest> { get<ArticleRequest> {
voter.assert { canView(it.article, citizenOrNull) } ac.assert { canView(it.article, citizenOrNull) }
Output( Output(
it.article, it.article,

View File

@@ -1,9 +1,9 @@
package fr.dcproject.component.article.routes package fr.dcproject.component.article.routes
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleForUpdate import fr.dcproject.component.article.ArticleForUpdate
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
@@ -11,7 +11,7 @@ import fr.dcproject.component.workgroup.WorkgroupRef
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.event.ArticleUpdate import fr.dcproject.event.ArticleUpdate
import fr.dcproject.event.raiseEvent import fr.dcproject.event.raiseEvent
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -39,7 +39,7 @@ object UpsertArticle {
) )
} }
fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: WorkgroupRepository, voter: ArticleVoter) { fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: WorkgroupRepository, ac: ArticleAccessControl) {
suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receive<Input>().run { suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receive<Input>().run {
ArticleForUpdate( ArticleForUpdate(
id = id ?: UUID.randomUUID(), id = id ?: UUID.randomUUID(),
@@ -57,7 +57,7 @@ object UpsertArticle {
post<UpsertArticleRequest> { post<UpsertArticleRequest> {
val article = call.convertRequestToEntity() val article = call.convertRequestToEntity()
voter.assert { canUpsert(article, citizenOrNull) } ac.assert { canUpsert(article, citizenOrNull) }
val newArticle: ArticleForView = repo.upsert(article) ?: error("Article not updated") val newArticle: ArticleForView = repo.upsert(article) ?: error("Article not updated")
call.respond(newArticle) call.respond(newArticle)
raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle)) raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle))

View File

@@ -1,25 +1,25 @@
package fr.dcproject.component.citizen package fr.dcproject.component.citizen
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
import fr.postgresjson.entity.EntityDeletedAt import fr.postgresjson.entity.EntityDeletedAt
class CitizenVoter : Voter() { class CitizenAccessControl : AccessControl() {
fun <S> canView(subjects: List<S>, connectedCitizen: CitizenI?): VoterResponse where S : CitizenI, S : EntityDeletedAt = fun <S> canView(subjects: List<S>, connectedCitizen: CitizenI?): AccessResponse where S : CitizenI, S : EntityDeletedAt =
canAll(subjects) { canView(it, connectedCitizen) } canAll(subjects) { canView(it, connectedCitizen) }
fun <S> canView(subject: S, connectedCitizen: CitizenI?): VoterResponse where S : CitizenI, S : EntityDeletedAt { fun <S> canView(subject: S, connectedCitizen: CitizenI?): AccessResponse where S : CitizenI, S : EntityDeletedAt {
if (connectedCitizen == null) return denied("You must be connected to view citizen", "citizen.view.connected") if (connectedCitizen == null) return denied("You must be connected to view citizen", "citizen.view.connected")
return if (subject.isDeleted()) denied("You cannot view a deleted citizen", "citizen.view.deleted") return if (subject.isDeleted()) denied("You cannot view a deleted citizen", "citizen.view.deleted")
else granted() else granted()
} }
fun <S : CitizenI> canUpdate(subject: S, connectedCitizen: CitizenI?): VoterResponse { fun <S : CitizenI> canUpdate(subject: S, connectedCitizen: CitizenI?): AccessResponse {
if (connectedCitizen == null) return denied("You must be connected to update Citizen", "citizen.update.notConnected") if (connectedCitizen == null) return denied("You must be connected to update Citizen", "citizen.update.notConnected")
return if (subject.id == connectedCitizen.id) granted() else denied("You can only update your citizen", "citizen.update.notYours") return if (subject.id == connectedCitizen.id) granted() else denied("You can only update your citizen", "citizen.update.notYours")
} }
fun <S : CitizenI> canChangePassword(subject: S, connectedCitizen: CitizenI?): VoterResponse { fun <S : CitizenI> canChangePassword(subject: S, connectedCitizen: CitizenI?): AccessResponse {
if (connectedCitizen == null) return denied("You must be connected to change your password", "citizen.changePassword.notConnected") if (connectedCitizen == null) return denied("You must be connected to change your password", "citizen.changePassword.notConnected")
return if (subject.id == connectedCitizen.id) granted() else denied("You can only change your password", "citizen.password.notYours") return if (subject.id == connectedCitizen.id) granted() else denied("You can only change your password", "citizen.password.notYours")
} }

View File

@@ -5,8 +5,8 @@ import fr.dcproject.component.auth.UserRepository
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenVoter import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.auth.UserPasswordCredential import io.ktor.auth.UserPasswordCredential
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -24,9 +24,9 @@ object ChangeMyPassword {
data class Input(val oldPassword: String, val newPassword: String) data class Input(val oldPassword: String, val newPassword: String)
} }
fun Route.changeMyPassword(voter: CitizenVoter, userRepository: UserRepository) { fun Route.changeMyPassword(ac: CitizenAccessControl, userRepository: UserRepository) {
put<ChangePasswordCitizenRequest> { put<ChangePasswordCitizenRequest> {
voter.assert { canChangePassword(it.citizen, citizenOrNull) } ac.assert { canChangePassword(it.citizen, citizenOrNull) }
try { try {
val content = call.receive<ChangePasswordCitizenRequest.Input>() val content = call.receive<ChangePasswordCitizenRequest.Input>()
val currentUser = userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword)) val currentUser = userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword))

View File

@@ -1,11 +1,11 @@
package fr.dcproject.component.citizen.routes package fr.dcproject.component.citizen.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.CitizenRepository import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.citizen.CitizenVoter
import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI import fr.dcproject.routes.PaginatedRequestI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -25,10 +25,10 @@ object FindCitizens {
val search: String? = null val search: String? = null
) : PaginatedRequestI by PaginatedRequest(page, limit) ) : PaginatedRequestI by PaginatedRequest(page, limit)
fun Route.findCitizen(voter: CitizenVoter, repo: CitizenRepository) { fun Route.findCitizen(ac: CitizenAccessControl, repo: CitizenRepository) {
get<CitizensRequest> { get<CitizensRequest> {
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search) val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
voter.assert { canView(citizens.result, citizenOrNull) } ac.assert { canView(citizens.result, citizenOrNull) }
call.respond(citizens) call.respond(citizens)
} }
} }

View File

@@ -2,8 +2,8 @@ package fr.dcproject.component.citizen.routes
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenVoter import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -17,13 +17,13 @@ object GetCurrentCitizen {
@Location("/citizens/current") @Location("/citizens/current")
class CurrentCitizenRequest class CurrentCitizenRequest
fun Route.getCurrentCitizen(voter: CitizenVoter) { fun Route.getCurrentCitizen(ac: CitizenAccessControl) {
get<CurrentCitizenRequest> { get<CurrentCitizenRequest> {
val currentUser = citizenOrNull val currentUser = citizenOrNull
if (currentUser === null) { if (currentUser === null) {
call.respond(HttpStatusCode.Unauthorized) call.respond(HttpStatusCode.Unauthorized)
} else { } else {
voter.assert { canView(currentUser, citizenOrNull) } ac.assert { canView(currentUser, citizenOrNull) }
call.respond(citizen) call.respond(citizen)
} }
} }

View File

@@ -2,8 +2,8 @@ package fr.dcproject.component.citizen.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenVoter import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -16,9 +16,9 @@ object GetOneCitizen {
@Location("/citizens/{citizen}") @Location("/citizens/{citizen}")
class CitizenRequest(val citizen: Citizen) class CitizenRequest(val citizen: Citizen)
fun Route.getOneCitizen(voter: CitizenVoter) { fun Route.getOneCitizen(ac: CitizenAccessControl) {
get<CitizenRequest> { get<CitizenRequest> {
voter.assert { canView(it.citizen, citizenOrNull) } ac.assert { canView(it.citizen, citizenOrNull) }
call.respond(it.citizen) call.respond(it.citizen)
} }

View File

@@ -4,9 +4,9 @@ import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentForUpdate import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -36,10 +36,10 @@ object CreateCommentArticle {
} }
} }
fun Route.createCommentArticle(repo: CommentArticleRepository, voter: CommentVoter) { fun Route.createCommentArticle(repo: CommentArticleRepository, ac: CommentAccessControl) {
post<PostArticleCommentRequest> { post<PostArticleCommentRequest> {
it.getComment(call).let { comment -> it.getComment(call).let { comment ->
voter.assert { canCreate(comment, citizenOrNull) } ac.assert { canCreate(comment, citizenOrNull) }
repo.comment(comment) repo.comment(comment)
call.respond(HttpStatusCode.Created, comment) call.respond(HttpStatusCode.Created, comment)
} }

View File

@@ -3,10 +3,10 @@ package fr.dcproject.component.comment.article.routes
import fr.dcproject.component.article.ArticleRef import fr.dcproject.component.article.ArticleRef
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI import fr.dcproject.routes.PaginatedRequestI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -28,11 +28,11 @@ object GetArticleComments {
val sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.fromString(sort) ?: CommentArticleRepository.Sort.CREATED_AT val sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.fromString(sort) ?: CommentArticleRepository.Sort.CREATED_AT
} }
fun Route.getArticleComments(repo: CommentArticleRepository, voter: CommentVoter) { fun Route.getArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
get<ArticleCommentsRequest> { get<ArticleCommentsRequest> {
val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort) val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort)
if (comment.result.isNotEmpty()) { if (comment.result.isNotEmpty()) {
voter.assert { canView(comment.result, citizenOrNull) } ac.assert { canView(comment.result, citizenOrNull) }
} }
call.respond(HttpStatusCode.OK, comment) call.respond(HttpStatusCode.OK, comment)
} }

View File

@@ -3,8 +3,8 @@ package fr.dcproject.component.comment.article.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.comment.article.CommentArticleRepository import fr.dcproject.component.comment.article.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -17,10 +17,10 @@ object GetCitizenArticleComments {
@Location("/citizens/{citizen}/comments/articles") @Location("/citizens/{citizen}/comments/articles")
class CitizenCommentArticleRequest(val citizen: Citizen) class CitizenCommentArticleRequest(val citizen: Citizen)
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, voter: CommentVoter) { fun Route.getCitizenArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
get<CitizenCommentArticleRequest> { get<CitizenCommentArticleRequest> {
repo.findByCitizen(it.citizen).let { comments -> repo.findByCitizen(it.citizen).let { comments ->
voter.assert { canView(comments.result, citizenOrNull) } ac.assert { canView(comments.result, citizenOrNull) }
call.respond(comments) call.respond(comments)
} }
} }

View File

@@ -2,24 +2,24 @@ package fr.dcproject.component.comment.generic
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.HasTarget import fr.dcproject.entity.HasTarget
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
import fr.postgresjson.entity.EntityCreatedBy import fr.postgresjson.entity.EntityCreatedBy
import fr.postgresjson.entity.EntityDeletedAt import fr.postgresjson.entity.EntityDeletedAt
class CommentVoter : Voter() { class CommentAccessControl : AccessControl() {
fun <S> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse fun <S> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse
where S : CommentI, where S : CommentI,
S : EntityDeletedAt = canAll(subjects) { canView(it, citizen) } S : EntityDeletedAt = canAll(subjects) { canView(it, citizen) }
fun <S> canView(subject: S, citizen: CitizenI?): VoterResponse fun <S> canView(subject: S, citizen: CitizenI?): AccessResponse
where S : CommentI, where S : CommentI,
S : EntityDeletedAt = when { S : EntityDeletedAt = when {
subject.isDeleted() -> denied("Your cannot view a deleted comment", "comment.view.deleted") subject.isDeleted() -> denied("Your cannot view a deleted comment", "comment.view.deleted")
else -> granted() else -> granted()
} }
fun <S, CR : CitizenI> canCreate(subject: S, citizen: CitizenI?): VoterResponse fun <S, CR : CitizenI> canCreate(subject: S, citizen: CitizenI?): AccessResponse
where S : CommentI, where S : CommentI,
S : EntityCreatedBy<CR>, S : EntityCreatedBy<CR>,
S : CommentWithParentI<*>, S : CommentWithParentI<*>,
@@ -31,7 +31,7 @@ class CommentVoter : Voter() {
else -> granted() else -> granted()
} }
fun <S, CR : CitizenI> canUpdate(subject: S, citizen: CitizenI?): VoterResponse fun <S, CR : CitizenI> canUpdate(subject: S, citizen: CitizenI?): AccessResponse
where S : CommentI, where S : CommentI,
S : EntityCreatedBy<CR> = when { S : EntityCreatedBy<CR> = when {
citizen == null -> denied("You must be connected to update comment", "comment.update.notConnected") citizen == null -> denied("You must be connected to update comment", "comment.update.notConnected")

View File

@@ -2,11 +2,11 @@ package fr.dcproject.component.comment.generic.routes
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentForUpdate import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.component.comment.generic.CommentRef import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.comment.generic.CommentRepository import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.features.NotFoundException import io.ktor.features.NotFoundException
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -24,7 +24,7 @@ object CreateCommentChildren {
class Input(val content: String) class Input(val content: String)
} }
fun Route.createCommentChildren(repo: CommentRepository, voter: CommentVoter) { fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
post<CreateCommentChildrenRequest> { post<CreateCommentChildrenRequest> {
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found") val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
val newComment = CommentForUpdate( val newComment = CommentForUpdate(
@@ -33,7 +33,7 @@ object CreateCommentChildren {
parent = parent parent = parent
) )
voter.assert { canCreate(newComment, citizenOrNull) } ac.assert { canCreate(newComment, citizenOrNull) }
repo.comment(newComment) repo.comment(newComment)
call.respond(HttpStatusCode.Created, newComment) call.respond(HttpStatusCode.Created, newComment)

View File

@@ -1,10 +1,10 @@
package fr.dcproject.component.comment.generic.routes package fr.dcproject.component.comment.generic.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentRef import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.comment.generic.CommentRepository import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.features.NotFoundException import io.ktor.features.NotFoundException
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -20,10 +20,10 @@ object EditComment {
@Location("/comments/{comment}") @Location("/comments/{comment}")
class EditCommentRequest(val comment: CommentRef) class EditCommentRequest(val comment: CommentRef)
fun Route.editComment(repo: CommentRepository, voter: CommentVoter) { fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) {
put<EditCommentRequest> { put<EditCommentRequest> {
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found") val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
voter.assert { canUpdate(comment, citizenOrNull) } ac.assert { canUpdate(comment, citizenOrNull) }
comment.content = call.receiveText() comment.content = call.receiveText()
repo.edit(comment) repo.edit(comment)

View File

@@ -1,11 +1,11 @@
package fr.dcproject.component.comment.generic.routes package fr.dcproject.component.comment.generic.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentRepository import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter
import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI import fr.dcproject.routes.PaginatedRequestI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -25,7 +25,7 @@ object GetCommentChildren {
val search: String? = null val search: String? = null
) : PaginatedRequestI by PaginatedRequest(page, limit) ) : PaginatedRequestI by PaginatedRequest(page, limit)
fun Route.getChildrenComments(repo: CommentRepository, voter: CommentVoter) { fun Route.getChildrenComments(repo: CommentRepository, ac: CommentAccessControl) {
get<CommentChildrenRequest> { get<CommentChildrenRequest> {
val comments = val comments =
repo.findByParent( repo.findByParent(
@@ -34,7 +34,7 @@ object GetCommentChildren {
it.limit it.limit
) )
voter.assert { canView(comments.result, citizenOrNull) } ac.assert { canView(comments.result, citizenOrNull) }
call.respond(HttpStatusCode.OK, comments) call.respond(HttpStatusCode.OK, comments)
} }

View File

@@ -1,10 +1,10 @@
package fr.dcproject.component.comment.generic.routes package fr.dcproject.component.comment.generic.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentRef import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.comment.generic.CommentRepository import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.features.NotFoundException import io.ktor.features.NotFoundException
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -19,10 +19,10 @@ object GetOneComment {
@Location("/comments/{comment}") @Location("/comments/{comment}")
class CommentRequest(val comment: CommentRef) class CommentRequest(val comment: CommentRef)
fun Route.getOneComment(repo: CommentRepository, voter: CommentVoter) { fun Route.getOneComment(repo: CommentRepository, ac: CommentAccessControl) {
get<CommentRequest> { get<CommentRequest> {
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found") val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
voter.assert { canView(comment, citizenOrNull) } ac.assert { canView(comment, citizenOrNull) }
call.respond(HttpStatusCode.OK, comment) call.respond(HttpStatusCode.OK, comment)
} }

View File

@@ -1,25 +1,25 @@
package fr.dcproject.component.follow package fr.dcproject.component.follow
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
import fr.dcproject.component.follow.Follow as FollowEntity import fr.dcproject.component.follow.Follow as FollowEntity
class FollowVoter : Voter() { class FollowAccessControl : AccessControl() {
fun canCreate(subject: FollowI, citizen: CitizenI?): VoterResponse { fun canCreate(subject: FollowI, citizen: CitizenI?): AccessResponse {
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()
} }
fun canDelete(subject: FollowI, citizen: CitizenI?): VoterResponse { fun canDelete(subject: FollowI, citizen: CitizenI?): AccessResponse {
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()
} }
fun <S : FollowEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse = fun <S : FollowEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun canView(subject: FollowEntity<*>, citizen: CitizenI?): VoterResponse { fun canView(subject: FollowEntity<*>, citizen: CitizenI?): AccessResponse {
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")
} }

View File

@@ -3,10 +3,10 @@ package fr.dcproject.component.follow.routes.article
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.auth.citizenOrNull
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowArticleRepository import fr.dcproject.component.follow.FollowArticleRepository
import fr.dcproject.component.follow.FollowForUpdate import fr.dcproject.component.follow.FollowForUpdate
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -20,10 +20,10 @@ object FollowArticle {
@Location("/articles/{article}/follows") @Location("/articles/{article}/follows")
class ArticleFollowRequest(val article: ArticleRef) class ArticleFollowRequest(val article: ArticleRef)
fun Route.followArticle(repo: FollowArticleRepository, voter: FollowVoter) { fun Route.followArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
post<ArticleFollowRequest> { post<ArticleFollowRequest> {
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen) val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
voter.assert { canCreate(follow, citizenOrNull) } ac.assert { canCreate(follow, citizenOrNull) }
repo.follow(follow) repo.follow(follow)
call.respond(HttpStatusCode.Created) call.respond(HttpStatusCode.Created)
} }

View File

@@ -3,9 +3,9 @@ package fr.dcproject.component.follow.routes.article
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.auth.citizenOrNull
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowArticleRepository import fr.dcproject.component.follow.FollowArticleRepository
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -19,10 +19,10 @@ object GetFollowArticle {
@Location("/articles/{article}/follows") @Location("/articles/{article}/follows")
class ArticleFollowRequest(val article: ArticleRef) class ArticleFollowRequest(val article: ArticleRef)
fun Route.getFollowArticle(repo: FollowArticleRepository, voter: FollowVoter) { fun Route.getFollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
get<ArticleFollowRequest> { get<ArticleFollowRequest> {
repo.findFollow(citizen, it.article)?.let { follow -> repo.findFollow(citizen, it.article)?.let { follow ->
voter.assert { canView(follow, citizenOrNull) } ac.assert { canView(follow, citizenOrNull) }
call.respond(follow) call.respond(follow)
} ?: call.respond(HttpStatusCode.NoContent) } ?: call.respond(HttpStatusCode.NoContent)
} }

View File

@@ -2,9 +2,9 @@ package fr.dcproject.component.follow.routes.article
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowArticleRepository import fr.dcproject.component.follow.FollowArticleRepository
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -17,10 +17,10 @@ object GetMyFollowsArticle {
@Location("/citizens/{citizen}/follows/articles") @Location("/citizens/{citizen}/follows/articles")
class CitizenFollowArticleRequest(val citizen: Citizen) class CitizenFollowArticleRequest(val citizen: Citizen)
fun Route.getMyFollowsArticle(repo: FollowArticleRepository, voter: FollowVoter) { fun Route.getMyFollowsArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
get<CitizenFollowArticleRequest> { get<CitizenFollowArticleRequest> {
val follows = repo.findByCitizen(it.citizen) val follows = repo.findByCitizen(it.citizen)
voter.assert { canView(follows.result, citizenOrNull) } ac.assert { canView(follows.result, citizenOrNull) }
call.respond(follows) call.respond(follows)
} }
} }

View File

@@ -3,10 +3,10 @@ package fr.dcproject.component.follow.routes.article
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.auth.citizenOrNull
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowArticleRepository import fr.dcproject.component.follow.FollowArticleRepository
import fr.dcproject.component.follow.FollowForUpdate import fr.dcproject.component.follow.FollowForUpdate
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -20,10 +20,10 @@ object UnfollowArticle {
@Location("/articles/{article}/follows") @Location("/articles/{article}/follows")
class ArticleFollowRequest(val article: ArticleRef) class ArticleFollowRequest(val article: ArticleRef)
fun Route.unfollowArticle(repo: FollowArticleRepository, voter: FollowVoter) { fun Route.unfollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
delete<ArticleFollowRequest> { delete<ArticleFollowRequest> {
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen) val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
voter.assert { canDelete(follow, citizenOrNull) } ac.assert { canDelete(follow, citizenOrNull) }
repo.unfollow(follow) repo.unfollow(follow)
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }

View File

@@ -2,11 +2,11 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowConstitutionRepository import fr.dcproject.component.follow.FollowConstitutionRepository
import fr.dcproject.component.follow.FollowForUpdate import fr.dcproject.component.follow.FollowForUpdate
import fr.dcproject.component.follow.FollowVoter
import fr.dcproject.entity.ConstitutionRef import fr.dcproject.entity.ConstitutionRef
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -20,10 +20,10 @@ object FollowConstitution {
@Location("/constitutions/{constitution}/follows") @Location("/constitutions/{constitution}/follows")
class ConstitutionFollowRequest(val constitution: ConstitutionRef) class ConstitutionFollowRequest(val constitution: ConstitutionRef)
fun Route.followConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) { fun Route.followConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
post<ConstitutionFollowRequest> { post<ConstitutionFollowRequest> {
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen) val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
voter.assert { canCreate(follow, citizenOrNull) } ac.assert { canCreate(follow, citizenOrNull) }
repo.follow(follow) repo.follow(follow)
call.respond(HttpStatusCode.Created) call.respond(HttpStatusCode.Created)
} }

View File

@@ -2,10 +2,10 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowConstitutionRepository import fr.dcproject.component.follow.FollowConstitutionRepository
import fr.dcproject.component.follow.FollowVoter
import fr.dcproject.entity.ConstitutionRef import fr.dcproject.entity.ConstitutionRef
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -19,10 +19,10 @@ object GetFollowConstitution {
@Location("/constitutions/{constitution}/follows") @Location("/constitutions/{constitution}/follows")
class ConstitutionFollowRequest(val constitution: ConstitutionRef) class ConstitutionFollowRequest(val constitution: ConstitutionRef)
fun Route.getFollowConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) { fun Route.getFollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
get<ConstitutionFollowRequest> { get<ConstitutionFollowRequest> {
repo.findFollow(citizen, it.constitution)?.let { follow -> repo.findFollow(citizen, it.constitution)?.let { follow ->
voter.assert { canView(follow, citizenOrNull) } ac.assert { canView(follow, citizenOrNull) }
call.respond(follow) call.respond(follow)
} ?: call.respond(HttpStatusCode.NotFound) } ?: call.respond(HttpStatusCode.NotFound)
} }

View File

@@ -2,9 +2,9 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowConstitutionRepository import fr.dcproject.component.follow.FollowConstitutionRepository
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -17,10 +17,10 @@ object GetMyFollowsConstitution {
@Location("/citizens/{citizen}/follows/constitutions") @Location("/citizens/{citizen}/follows/constitutions")
class CitizenFollowConstitutionRequest(val citizen: CitizenRef) class CitizenFollowConstitutionRequest(val citizen: CitizenRef)
fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) { fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
get<CitizenFollowConstitutionRequest> { get<CitizenFollowConstitutionRequest> {
val follows = repo.findByCitizen(it.citizen) val follows = repo.findByCitizen(it.citizen)
voter.assert { canView(follows.result, citizenOrNull) } ac.assert { canView(follows.result, citizenOrNull) }
call.respond(follows) call.respond(follows)
} }
} }

View File

@@ -2,11 +2,11 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.FollowConstitutionRepository import fr.dcproject.component.follow.FollowConstitutionRepository
import fr.dcproject.component.follow.FollowForUpdate import fr.dcproject.component.follow.FollowForUpdate
import fr.dcproject.component.follow.FollowVoter
import fr.dcproject.entity.ConstitutionRef import fr.dcproject.entity.ConstitutionRef
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -20,10 +20,10 @@ object UnfollowConstitution {
@Location("/constitutions/{constitution}/follows") @Location("/constitutions/{constitution}/follows")
class ConstitutionUnfollowRequest(val constitution: ConstitutionRef) class ConstitutionUnfollowRequest(val constitution: ConstitutionRef)
fun Route.unfollowConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) { fun Route.unfollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
delete<ConstitutionUnfollowRequest> { delete<ConstitutionUnfollowRequest> {
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen) val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
voter.assert { canDelete(follow, citizenOrNull) } ac.assert { canDelete(follow, citizenOrNull) }
repo.unfollow(follow) repo.unfollow(follow)
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }

View File

@@ -3,17 +3,17 @@ package fr.dcproject.component.opinion
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.opinion.entity.OpinionI import fr.dcproject.component.opinion.entity.OpinionI
import fr.dcproject.entity.HasTarget import fr.dcproject.entity.HasTarget
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
import fr.postgresjson.entity.EntityCreatedBy import fr.postgresjson.entity.EntityCreatedBy
import fr.postgresjson.entity.EntityDeletedAt import fr.postgresjson.entity.EntityDeletedAt
class OpinionVoter : Voter() { class OpinionAccessControl : AccessControl() {
fun <S> canCreate(subjects: List<S>, citizen: CitizenI?): VoterResponse where S : OpinionI, S : HasTarget<*> = fun <S> canCreate(subjects: List<S>, citizen: CitizenI?): AccessResponse where S : OpinionI, S : HasTarget<*> =
canAll(subjects) { canCreate(it, citizen) } canAll(subjects) { canCreate(it, citizen) }
fun <S> canCreate(subject: S, citizen: CitizenI?): VoterResponse where S : OpinionI, S : HasTarget<*> { fun <S> canCreate(subject: S, citizen: CitizenI?): AccessResponse where S : OpinionI, S : HasTarget<*> {
val target = subject.target val target = subject.target
return when { return when {
citizen == null -> denied("You must be connected to make an opinion", "opinion.create.notConnected") citizen == null -> denied("You must be connected to make an opinion", "opinion.create.notConnected")
@@ -22,12 +22,12 @@ class OpinionVoter : Voter() {
} }
} }
fun <S : OpinionI, SS : List<S>> canView(subjects: SS, citizen: CitizenI?): VoterResponse = fun <S : OpinionI, SS : List<S>> canView(subjects: SS, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun <S : OpinionI> canView(subject: S, citizen: CitizenI?): VoterResponse = granted() fun <S : OpinionI> canView(subject: S, citizen: CitizenI?): AccessResponse = granted()
fun <S, C : CitizenI> canDelete(subject: S, citizen: CitizenI?): VoterResponse where S : EntityCreatedBy<C>, S : OpinionI = when { fun <S, C : CitizenI> canDelete(subject: S, citizen: CitizenI?): AccessResponse where S : EntityCreatedBy<C>, S : OpinionI = when {
citizen == null -> denied("You must be connected to delete opinion", "opinion.delete.notConnected") citizen == null -> denied("You must be connected to delete opinion", "opinion.delete.notConnected")
subject.createdBy.id != citizen.id -> denied("You can only delete your opinions", "opinion.delete.notYours") subject.createdBy.id != citizen.id -> denied("You can only delete your opinions", "opinion.delete.notYours")
else -> granted() else -> granted()

View File

@@ -2,14 +2,14 @@ package fr.dcproject.component.opinion
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.opinion.entity.OpinionChoice import fr.dcproject.component.opinion.entity.OpinionChoice
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
class OpinionChoiceVoter : Voter() { class OpinionChoiceAccessControl : AccessControl() {
fun canView(subjects: List<OpinionChoice>, citizen: CitizenI?): VoterResponse = fun canView(subjects: List<OpinionChoice>, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun canView(subject: OpinionChoice, citizen: CitizenI?): VoterResponse { fun canView(subject: OpinionChoice, citizen: CitizenI?): AccessResponse {
return granted() return granted()
} }
} }

View File

@@ -2,10 +2,10 @@ package fr.dcproject.component.opinion.routes
import fr.dcproject.component.article.ArticleRef import fr.dcproject.component.article.ArticleRef
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.opinion.OpinionVoter import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.entity.Opinion import fr.dcproject.component.opinion.entity.Opinion
import fr.dcproject.security.assert
import fr.dcproject.utils.toUUID import fr.dcproject.utils.toUUID
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -27,10 +27,10 @@ object GetCitizenOpinions {
val id: List<UUID> = id.toUUID() val id: List<UUID> = id.toUUID()
} }
fun Route.getCitizenOpinions(repo: OpinionArticleRepository, voter: OpinionVoter) { fun Route.getCitizenOpinions(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
get<CitizenOpinions> { get<CitizenOpinions> {
val opinionsEntities: List<Opinion<ArticleRef>> = repo.findCitizenOpinionsByTargets(it.citizen, it.id) val opinionsEntities: List<Opinion<ArticleRef>> = repo.findCitizenOpinionsByTargets(it.citizen, it.id)
voter.assert { canView(opinionsEntities, citizenOrNull) } ac.assert { canView(opinionsEntities, citizenOrNull) }
call.respond(opinionsEntities) call.respond(opinionsEntities)
} }

View File

@@ -3,10 +3,10 @@ package fr.dcproject.component.opinion.routes
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.opinion.OpinionVoter import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI import fr.dcproject.routes.PaginatedRequestI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -27,10 +27,10 @@ object GetMyOpinionsArticle {
limit: Int = 50 limit: Int = 50
) : PaginatedRequestI by PaginatedRequest(page, limit) ) : PaginatedRequestI by PaginatedRequest(page, limit)
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, voter: OpinionVoter) { fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
get<CitizenOpinionsArticleRequest> { get<CitizenOpinionsArticleRequest> {
val opinions = repo.findCitizenOpinions(citizen, it.page, it.limit) val opinions = repo.findCitizenOpinions(citizen, it.page, it.limit)
voter.assert { canView(opinions.result, citizenOrNull) } ac.assert { canView(opinions.result, citizenOrNull) }
call.respond(opinions) call.respond(opinions)
} }
} }

View File

@@ -1,9 +1,9 @@
package fr.dcproject.component.opinion.routes package fr.dcproject.component.opinion.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.opinion.OpinionChoiceVoter import fr.dcproject.component.opinion.OpinionChoiceAccessControl
import fr.dcproject.component.opinion.entity.OpinionChoice import fr.dcproject.component.opinion.entity.OpinionChoice
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -16,9 +16,9 @@ object GetOpinionChoice {
@Location("/opinions/{opinionChoice}") @Location("/opinions/{opinionChoice}")
class OpinionChoiceRequest(val opinionChoice: OpinionChoice) class OpinionChoiceRequest(val opinionChoice: OpinionChoice)
fun Route.getOpinionChoice(voter: OpinionChoiceVoter) { fun Route.getOpinionChoice(ac: OpinionChoiceAccessControl) {
get<OpinionChoiceRequest> { get<OpinionChoiceRequest> {
voter.assert { canView(it.opinionChoice, citizenOrNull) } ac.assert { canView(it.opinionChoice, citizenOrNull) }
call.respond(it.opinionChoice) call.respond(it.opinionChoice)
} }

View File

@@ -1,9 +1,9 @@
package fr.dcproject.component.opinion.routes package fr.dcproject.component.opinion.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.opinion.OpinionChoiceAccessControl
import fr.dcproject.component.opinion.OpinionChoiceRepository import fr.dcproject.component.opinion.OpinionChoiceRepository
import fr.dcproject.component.opinion.OpinionChoiceVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -16,10 +16,10 @@ object GetOpinionChoices {
@Location("/opinions") @Location("/opinions")
class OpinionChoicesRequest(val targets: List<String> = emptyList()) class OpinionChoicesRequest(val targets: List<String> = emptyList())
fun Route.getOpinionChoices(repo: OpinionChoiceRepository, voter: OpinionChoiceVoter) { fun Route.getOpinionChoices(repo: OpinionChoiceRepository, ac: OpinionChoiceAccessControl) {
get<OpinionChoicesRequest> { get<OpinionChoicesRequest> {
val opinionChoices = repo.findOpinionsChoices(it.targets) val opinionChoices = repo.findOpinionsChoices(it.targets)
voter.assert { canView(opinionChoices, citizenOrNull) } ac.assert { canView(opinionChoices, citizenOrNull) }
call.respond(opinionChoices) call.respond(opinionChoices)
} }

View File

@@ -3,11 +3,11 @@ package fr.dcproject.component.opinion.routes
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.opinion.OpinionVoter import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.entity.OpinionChoiceRef import fr.dcproject.component.opinion.entity.OpinionChoiceRef
import fr.dcproject.component.opinion.entity.OpinionForUpdate import fr.dcproject.component.opinion.entity.OpinionForUpdate
import fr.dcproject.security.assert
import fr.dcproject.utils.toUUID import fr.dcproject.utils.toUUID
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -31,7 +31,7 @@ object OpinionArticle {
} }
} }
fun Route.setOpinionOnArticle(repo: OpinionArticleRepository, voter: OpinionVoter) { fun Route.setOpinionOnArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
put<ArticleOpinion> { put<ArticleOpinion> {
call.receive<ArticleOpinion.Body>().ids.map { id -> call.receive<ArticleOpinion.Body>().ids.map { id ->
OpinionForUpdate( OpinionForUpdate(
@@ -40,7 +40,7 @@ object OpinionArticle {
createdBy = citizen createdBy = citizen
) )
}.let { opinions -> }.let { opinions ->
voter.assert { canCreate(opinions, citizenOrNull) } ac.assert { canCreate(opinions, citizenOrNull) }
repo.updateOpinions(opinions) repo.updateOpinions(opinions)
}.let { }.let {
call.respond(HttpStatusCode.Created, it) call.respond(HttpStatusCode.Created, it)

View File

@@ -3,22 +3,22 @@ package fr.dcproject.component.vote
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.vote.entity.VoteForUpdateI import fr.dcproject.component.vote.entity.VoteForUpdateI
import fr.dcproject.entity.TargetI import fr.dcproject.entity.TargetI
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
import fr.postgresjson.entity.EntityDeletedAt import fr.postgresjson.entity.EntityDeletedAt
import fr.dcproject.component.vote.entity.Vote as VoteEntity import fr.dcproject.component.vote.entity.Vote as VoteEntity
class VoteVoter : Voter() { class VoteAccessControl : AccessControl() {
fun <S> canCreate(subject: VoteForUpdateI<S, *>, citizen: CitizenI?): VoterResponse where S : EntityDeletedAt, S : TargetI = when { fun <S> canCreate(subject: VoteForUpdateI<S, *>, citizen: CitizenI?): AccessResponse where S : EntityDeletedAt, S : TargetI = when {
citizen == null -> denied("You must be connected for vote", "vote.create.connected") citizen == null -> denied("You must be connected for vote", "vote.create.connected")
subject.target.isDeleted() -> denied("You cannot vote on deleted target", "vote.create.isDeleted") subject.target.isDeleted() -> denied("You cannot vote on deleted target", "vote.create.isDeleted")
else -> granted() else -> granted()
} }
fun <S : VoteEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse = fun <S : VoteEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun canView(subject: VoteEntity<*>, citizen: CitizenI?): VoterResponse = when { fun canView(subject: VoteEntity<*>, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected for view your votes", "vote.view.connected") citizen == null -> denied("You must be connected for view your votes", "vote.view.connected")
subject.createdBy.id != citizen.id -> denied("You can only display your votes", "vote.view.onlyYours") subject.createdBy.id != citizen.id -> denied("You can only display your votes", "vote.view.onlyYours")
else -> granted() else -> granted()

View File

@@ -2,10 +2,10 @@ package fr.dcproject.component.vote.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.VoteRepository import fr.dcproject.component.vote.VoteRepository
import fr.dcproject.component.vote.VoteVoter import fr.dcproject.security.assert
import fr.dcproject.utils.toUUID import fr.dcproject.utils.toUUID
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -21,11 +21,11 @@ object GetCitizenVotes {
val id: List<UUID> = id.toUUID() val id: List<UUID> = id.toUUID()
} }
fun Route.getCitizenVote(repo: VoteRepository, voter: VoteVoter) { fun Route.getCitizenVote(repo: VoteRepository, ac: VoteAccessControl) {
get<CitizenVotesRequest> { get<CitizenVotesRequest> {
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id) val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
if (votes.isNotEmpty()) { if (votes.isNotEmpty()) {
voter.assert { canView(votes, citizenOrNull) } ac.assert { canView(votes, citizenOrNull) }
} }
call.respond(votes) call.respond(votes)
} }

View File

@@ -2,11 +2,11 @@ package fr.dcproject.component.vote.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.VoteArticleRepository import fr.dcproject.component.vote.VoteArticleRepository
import fr.dcproject.component.vote.VoteVoter
import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI import fr.dcproject.routes.PaginatedRequestI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location import io.ktor.locations.Location
@@ -24,10 +24,10 @@ object GetCitizenVotesOnArticle {
val search: String? = null val search: String? = null
) : PaginatedRequestI by PaginatedRequest(page, limit) ) : PaginatedRequestI by PaginatedRequest(page, limit)
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, voter: VoteVoter) { fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
get<CitizenVoteArticleRequest> { get<CitizenVoteArticleRequest> {
val votes = repo.findByCitizen(it.citizen, it.page, it.limit) val votes = repo.findByCitizen(it.citizen, it.page, it.limit)
voter.assert { canView(votes.result, citizenOrNull) } ac.assert { canView(votes.result, citizenOrNull) }
call.respond(votes) call.respond(votes)
} }

View File

@@ -3,10 +3,10 @@ package fr.dcproject.component.vote.routes
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.VoteArticleRepository import fr.dcproject.component.vote.VoteArticleRepository
import fr.dcproject.component.vote.VoteVoter
import fr.dcproject.component.vote.entity.VoteForUpdate import fr.dcproject.component.vote.entity.VoteForUpdate
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -23,7 +23,7 @@ object PutVoteOnArticle {
data class Content(var note: Int) data class Content(var note: Int)
} }
fun Route.putVoteOnArticle(repo: VoteArticleRepository, voter: VoteVoter) { fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
put<ArticleVoteRequest> { put<ArticleVoteRequest> {
val content = call.receive<ArticleVoteRequest.Content>() val content = call.receive<ArticleVoteRequest.Content>()
val vote = VoteForUpdate( val vote = VoteForUpdate(
@@ -31,7 +31,7 @@ object PutVoteOnArticle {
note = content.note, note = content.note,
createdBy = this.citizen createdBy = this.citizen
) )
voter.assert { canCreate(vote, citizenOrNull) } ac.assert { canCreate(vote, citizenOrNull) }
val votes = repo.vote(vote) val votes = repo.vote(vote)
call.respond(HttpStatusCode.Created, votes) call.respond(HttpStatusCode.Created, votes)
} }

View File

@@ -3,10 +3,10 @@ package fr.dcproject.component.vote.routes
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.comment.generic.CommentRepository import fr.dcproject.component.comment.generic.CommentRepository
import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.VoteCommentRepository import fr.dcproject.component.vote.VoteCommentRepository
import fr.dcproject.component.vote.VoteVoter
import fr.dcproject.component.vote.entity.VoteForUpdate import fr.dcproject.component.vote.entity.VoteForUpdate
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -24,7 +24,7 @@ object PutVoteOnComment {
data class Content(var note: Int) data class Content(var note: Int)
} }
fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, voter: VoteVoter) { fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, ac: VoteAccessControl) {
put<CommentVoteRequest> { put<CommentVoteRequest> {
val comment = commentRepo.findById(it.comment)!! val comment = commentRepo.findById(it.comment)!!
val content = call.receive<CommentVoteRequest.Content>() val content = call.receive<CommentVoteRequest.Content>()
@@ -33,7 +33,7 @@ object PutVoteOnComment {
note = content.note, note = content.note,
createdBy = this.citizen createdBy = this.citizen
) )
voter.assert { canCreate(vote, citizenOrNull) } ac.assert { canCreate(vote, citizenOrNull) }
val votes = voteCommentRepo.vote(vote) val votes = voteCommentRepo.vote(vote)
call.respond(HttpStatusCode.Created, votes) call.respond(HttpStatusCode.Created, votes)
} }

View File

@@ -2,11 +2,11 @@ package fr.dcproject.component.vote.routes
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.VoteConstitutionRepository import fr.dcproject.component.vote.VoteConstitutionRepository
import fr.dcproject.component.vote.VoteVoter
import fr.dcproject.component.vote.entity.VoteForUpdate import fr.dcproject.component.vote.entity.VoteForUpdate
import fr.dcproject.component.vote.routes.VoteConstitution.ConstitutionVoteRequest.Input import fr.dcproject.component.vote.routes.VoteConstitution.ConstitutionVoteRequest.Input
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -24,7 +24,7 @@ object VoteConstitution {
data class Input(var note: Int) data class Input(var note: Int)
} }
fun Route.voteConstitution(repo: VoteConstitutionRepository, voter: VoteVoter) { fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl) {
put<ConstitutionVoteRequest> { put<ConstitutionVoteRequest> {
val content = call.receive<Input>() val content = call.receive<Input>()
val vote = VoteForUpdate( val vote = VoteForUpdate(
@@ -32,7 +32,7 @@ object VoteConstitution {
note = content.note, note = content.note,
createdBy = this.citizen createdBy = this.citizen
) )
voter.assert { canCreate(vote, citizenOrNull) } ac.assert { canCreate(vote, citizenOrNull) }
repo.vote(vote) repo.vote(vote)
call.respond(HttpStatusCode.Created) call.respond(HttpStatusCode.Created)
} }

View File

@@ -2,48 +2,48 @@ package fr.dcproject.component.workgroup
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
class WorkgroupVoter : Voter() { class WorkgroupAccessControl : AccessControl() {
fun canCreate(subject: WorkgroupI, citizen: CitizenI?): VoterResponse { fun canCreate(subject: WorkgroupI, citizen: CitizenI?): AccessResponse {
if (citizen == null) return denied("You must be connected to create workgroup", "workgroup.create.notConnected") if (citizen == null) return denied("You must be connected to create workgroup", "workgroup.create.notConnected")
return granted() return granted()
} }
fun <S : WorkgroupWithAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse = fun <S : WorkgroupWithAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun canView(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): VoterResponse = fun canView(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): AccessResponse =
if (subject.isDeleted()) denied("You cannot view a deleted workgroup", "workgroup.view.deleted") if (subject.isDeleted()) denied("You cannot view a deleted workgroup", "workgroup.view.deleted")
else if (!subject.anonymous) granted() else if (!subject.anonymous) granted()
else if (subject.anonymous && citizen != null && subject.isMember(citizen)) granted() else if (subject.anonymous && citizen != null && subject.isMember(citizen)) granted()
else denied("You cannot view anonymous workgroup", "workgroup.view.anonymous") else denied("You cannot view anonymous workgroup", "workgroup.view.anonymous")
fun canDelete(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): VoterResponse { fun canDelete(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): AccessResponse {
if (citizen == null) return denied("You must be connected to delete workgroup", "workgroup.delete.notConnected") if (citizen == null) return denied("You must be connected to delete workgroup", "workgroup.delete.notConnected")
return if (subject.hasRole(Role.MASTER, citizen)) granted() return if (subject.hasRole(Role.MASTER, citizen)) granted()
else denied("You must hase role MASTER to delete workgroup", "workgroup.delete.role") else denied("You must hase role MASTER to delete workgroup", "workgroup.delete.role")
} }
fun canUpdate(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): VoterResponse { fun canUpdate(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): AccessResponse {
if (citizen == null) return denied("You must be connected to update workgroup", "workgroup.update.notConnected") if (citizen == null) return denied("You must be connected to update workgroup", "workgroup.update.notConnected")
return if (subject.hasRole(Role.MASTER, citizen)) granted() return if (subject.hasRole(Role.MASTER, citizen)) granted()
else denied("You must hase role MASTER to delete workgroup", "workgroup.delete.role") else denied("You must hase role MASTER to delete workgroup", "workgroup.delete.role")
} }
fun canAddMembers(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): VoterResponse = when { fun canAddMembers(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected to add member to the workgroup", "workgroup.addMember.notConnected") citizen == null -> denied("You must be connected to add member to the workgroup", "workgroup.addMember.notConnected")
subject.hasRole(Role.MASTER, citizen) -> granted() subject.hasRole(Role.MASTER, citizen) -> granted()
else -> denied("You must have MASTER Role for add member to workgroup", "workgroup.addMember.role") else -> denied("You must have MASTER Role for add member to workgroup", "workgroup.addMember.role")
} }
fun canUpdateMembers(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): VoterResponse = when { fun canUpdateMembers(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected to update member of the workgroup", "workgroup.updateMember.notConnected") citizen == null -> denied("You must be connected to update member of the workgroup", "workgroup.updateMember.notConnected")
subject.hasRole(Role.MASTER, citizen) -> granted() subject.hasRole(Role.MASTER, citizen) -> granted()
else -> denied("You must have MASTER Role for update members of workgroup", "workgroup.updateMember.role") else -> denied("You must have MASTER Role for update members of workgroup", "workgroup.updateMember.role")
} }
fun canRemoveMembers(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): VoterResponse = when { fun canRemoveMembers(subject: WorkgroupWithAuthI<*>, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected to remove member of the workgroup", "workgroup.removeMember.notConnected") citizen == null -> denied("You must be connected to remove member of the workgroup", "workgroup.removeMember.notConnected")
subject.hasRole(Role.MASTER, citizen) -> granted() subject.hasRole(Role.MASTER, citizen) -> granted()
else -> denied("You must have MASTER Role for remove members of workgroup", "workgroup.removeMember.role") else -> denied("You must have MASTER Role for remove members of workgroup", "workgroup.removeMember.role")

View File

@@ -2,11 +2,11 @@ package fr.dcproject.component.workgroup.routes
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupSimple import fr.dcproject.component.workgroup.WorkgroupSimple
import fr.dcproject.component.workgroup.WorkgroupVoter
import fr.dcproject.component.workgroup.routes.CreateWorkgroup.PostWorkgroupRequest.Input import fr.dcproject.component.workgroup.routes.CreateWorkgroup.PostWorkgroupRequest.Input
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -30,7 +30,7 @@ object CreateWorkgroup {
) )
} }
fun Route.createWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.createWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
post<PostWorkgroupRequest> { post<PostWorkgroupRequest> {
call.receive<Input>().run { call.receive<Input>().run {
WorkgroupSimple( WorkgroupSimple(
@@ -42,7 +42,7 @@ object CreateWorkgroup {
citizen citizen
) )
}.let { workgroup -> }.let { workgroup ->
voter.assert { canCreate(workgroup, citizenOrNull) } ac.assert { canCreate(workgroup, citizenOrNull) }
repo.upsert(workgroup) repo.upsert(workgroup)
}.let { }.let {
call.respond(HttpStatusCode.Created, it) call.respond(HttpStatusCode.Created, it)

View File

@@ -1,9 +1,9 @@
package fr.dcproject.component.workgroup.routes package fr.dcproject.component.workgroup.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -18,10 +18,10 @@ object DeleteWorkgroup {
@Location("/workgroups/{workgroupId}") @Location("/workgroups/{workgroupId}")
class DeleteWorkgroupRequest(val workgroupId: UUID) class DeleteWorkgroupRequest(val workgroupId: UUID)
fun Route.deleteWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.deleteWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
delete<DeleteWorkgroupRequest> { delete<DeleteWorkgroupRequest> {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
voter.assert { canDelete(workgroup, citizenOrNull) } ac.assert { canDelete(workgroup, citizenOrNull) }
repo.delete(workgroup) repo.delete(workgroup)
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} ?: call.respond(HttpStatusCode.NotFound) } ?: call.respond(HttpStatusCode.NotFound)

View File

@@ -1,10 +1,10 @@
package fr.dcproject.component.workgroup.routes package fr.dcproject.component.workgroup.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter
import fr.dcproject.component.workgroup.routes.EditWorkgroup.PutWorkgroupRequest.Input import fr.dcproject.component.workgroup.routes.EditWorkgroup.PutWorkgroupRequest.Input
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -28,7 +28,7 @@ object EditWorkgroup {
) )
} }
fun Route.editWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.editWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
put<PutWorkgroupRequest> { put<PutWorkgroupRequest> {
repo.findById(it.workgroupId)?.let { old -> repo.findById(it.workgroupId)?.let { old ->
call.receive<Input>().run { call.receive<Input>().run {
@@ -38,7 +38,7 @@ object EditWorkgroup {
logo = logo ?: old.logo, logo = logo ?: old.logo,
anonymous = anonymous ?: old.anonymous anonymous = anonymous ?: old.anonymous
).let { workgroup -> ).let { workgroup ->
voter.assert { canUpdate(workgroup, citizenOrNull) } ac.assert { canUpdate(workgroup, citizenOrNull) }
repo.upsert(workgroup) repo.upsert(workgroup)
call.respond(HttpStatusCode.OK, it) call.respond(HttpStatusCode.OK, it)
} }

View File

@@ -1,9 +1,9 @@
package fr.dcproject.component.workgroup.routes package fr.dcproject.component.workgroup.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -18,10 +18,10 @@ object GetWorkgroup {
@Location("/workgroups/{workgroupId}") @Location("/workgroups/{workgroupId}")
class WorkgroupRequest(val workgroupId: UUID) class WorkgroupRequest(val workgroupId: UUID)
fun Route.getWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.getWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
get<WorkgroupRequest> { get<WorkgroupRequest> {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
voter.assert { canView(workgroup, citizenOrNull) } ac.assert { canView(workgroup, citizenOrNull) }
call.respond(workgroup) call.respond(workgroup)
} ?: call.respond(HttpStatusCode.NotFound) } ?: call.respond(HttpStatusCode.NotFound)
} }

View File

@@ -1,10 +1,10 @@
package fr.dcproject.component.workgroup.routes package fr.dcproject.component.workgroup.routes
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter import fr.dcproject.security.assert
import fr.dcproject.utils.toUUID import fr.dcproject.utils.toUUID
import fr.dcproject.voter.assert
import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI
import io.ktor.application.call import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -31,7 +31,7 @@ object GetWorkgroups {
val members: List<UUID>? = members?.toUUID() val members: List<UUID>? = members?.toUUID()
} }
fun Route.getWorkgroups(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.getWorkgroups(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
get<WorkgroupsRequest> { get<WorkgroupsRequest> {
val workgroups = val workgroups =
repo.find( repo.find(
@@ -42,7 +42,7 @@ object GetWorkgroups {
it.search, it.search,
WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members) WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members)
) )
voter.assert { canView(workgroups.result, citizenOrNull) } ac.assert { canView(workgroups.result, citizenOrNull) }
call.respond(workgroups) call.respond(workgroups)
} }
} }

View File

@@ -2,10 +2,10 @@ package fr.dcproject.component.workgroup.routes.members
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter
import fr.dcproject.component.workgroup.WorkgroupWithMembersI import fr.dcproject.component.workgroup.WorkgroupWithMembersI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -40,12 +40,12 @@ object AddMemberToWorkgroup {
} }
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
/* Add members to workgroup */ /* Add members to workgroup */
post<WorkgroupsMembersRequest> { post<WorkgroupsMembersRequest> {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
voter.assert { canAddMembers(workgroup, citizenOrNull) } ac.assert { canAddMembers(workgroup, citizenOrNull) }
repo.addMembers(workgroup, members) repo.addMembers(workgroup, members)
}.let { members -> }.let { members ->
call.respond(HttpStatusCode.Created, members) call.respond(HttpStatusCode.Created, members)

View File

@@ -2,10 +2,10 @@ package fr.dcproject.component.workgroup.routes.members
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter
import fr.dcproject.component.workgroup.WorkgroupWithMembersI import fr.dcproject.component.workgroup.WorkgroupWithMembersI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -38,12 +38,12 @@ object DeleteMembersOfWorkgroup {
) )
} }
fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
/* Delete members of workgroup */ /* Delete members of workgroup */
delete<WorkgroupsMembersRequest> { delete<WorkgroupsMembersRequest> {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
voter.assert { canView(workgroup, citizenOrNull) } ac.assert { canView(workgroup, citizenOrNull) }
repo.removeMembers(workgroup, members) repo.removeMembers(workgroup, members)
}.let { members -> }.let { members ->
call.respond(HttpStatusCode.OK, members) call.respond(HttpStatusCode.OK, members)

View File

@@ -2,10 +2,10 @@ package fr.dcproject.component.workgroup.routes.members
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenRef import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupRepository import fr.dcproject.component.workgroup.WorkgroupRepository
import fr.dcproject.component.workgroup.WorkgroupVoter
import fr.dcproject.component.workgroup.WorkgroupWithMembersI import fr.dcproject.component.workgroup.WorkgroupWithMembersI
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -38,12 +38,12 @@ object UpdateMemberOfWorkgroup {
) )
} }
fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) { fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
/* Update members of workgroup */ /* Update members of workgroup */
put<WorkgroupsMembersRequest> { put<WorkgroupsMembersRequest> {
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
voter.assert { canUpdateMembers(workgroup, citizenOrNull) } ac.assert { canUpdateMembers(workgroup, citizenOrNull) }
repo.updateMembers(workgroup, members) repo.updateMembers(workgroup, members)
}.let { members -> }.let { members ->
call.respond(HttpStatusCode.OK, members) call.respond(HttpStatusCode.OK, members)

View File

@@ -3,11 +3,11 @@ 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.auth.citizenOrNull
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentForUpdate import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.component.comment.generic.CommentVoter
import fr.dcproject.entity.ConstitutionRef import fr.dcproject.entity.ConstitutionRef
import fr.dcproject.repository.CommentConstitutionRepository import fr.dcproject.repository.CommentConstitutionRepository
import fr.dcproject.voter.assert import fr.dcproject.security.assert
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -28,10 +28,10 @@ object CommentConstitutionPaths {
} }
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: CommentVoter) { fun Route.commentConstitution(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
get<CommentConstitutionPaths.ConstitutionCommentRequest> { get<CommentConstitutionPaths.ConstitutionCommentRequest> {
val comments = repo.findByTarget(it.constitution) val comments = repo.findByTarget(it.constitution)
voter.assert { canView(comments.result, citizenOrNull) } ac.assert { canView(comments.result, citizenOrNull) }
call.respond(HttpStatusCode.OK, comments) call.respond(HttpStatusCode.OK, comments)
} }
@@ -42,7 +42,7 @@ fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: Commen
createdBy = citizen, createdBy = citizen,
content = content content = content
) )
voter.assert { canCreate(comment, citizenOrNull) } ac.assert { canCreate(comment, citizenOrNull) }
repo.comment(comment) repo.comment(comment)
call.respond(HttpStatusCode.Created, comment) call.respond(HttpStatusCode.Created, comment)
@@ -50,7 +50,7 @@ fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: Commen
get<CommentConstitutionPaths.CitizenCommentConstitutionRequest> { get<CommentConstitutionPaths.CitizenCommentConstitutionRequest> {
val comments = repo.findByCitizen(it.citizen) val comments = repo.findByCitizen(it.citizen)
voter.assert { canView(comments.result, citizenOrNull) } ac.assert { canView(comments.result, citizenOrNull) }
call.respond(comments) call.respond(comments)
} }
} }

View File

@@ -6,8 +6,8 @@ import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.citizen.CitizenWithUserI import fr.dcproject.component.citizen.CitizenWithUserI
import fr.dcproject.entity.ConstitutionSimple import fr.dcproject.entity.ConstitutionSimple
import fr.dcproject.entity.ConstitutionSimple.TitleSimple import fr.dcproject.entity.ConstitutionSimple.TitleSimple
import fr.dcproject.security.voter.ConstitutionVoter import fr.dcproject.security.assert
import fr.dcproject.voter.assert import fr.dcproject.security.voter.ConstitutionAccessControl
import fr.postgresjson.entity.UuidEntity import fr.postgresjson.entity.UuidEntity
import fr.postgresjson.repository.RepositoryI import fr.postgresjson.repository.RepositoryI
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
@@ -88,21 +88,21 @@ object ConstitutionPaths {
} }
@KtorExperimentalLocationsAPI @KtorExperimentalLocationsAPI
fun Route.constitution(repo: ConstitutionRepository, voter: ConstitutionVoter) { fun Route.constitution(repo: ConstitutionRepository, ac: ConstitutionAccessControl) {
get<ConstitutionPaths.ConstitutionsRequest> { get<ConstitutionPaths.ConstitutionsRequest> {
val constitutions = repo.find(it.page, it.limit, it.sort, it.direction, it.search) val constitutions = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
voter.assert { canView(constitutions.result, citizenOrNull) } ac.assert { canView(constitutions.result, citizenOrNull) }
call.respond(constitutions) call.respond(constitutions)
} }
get<ConstitutionPaths.ConstitutionRequest> { get<ConstitutionPaths.ConstitutionRequest> {
voter.assert { canView(it.constitution, citizenOrNull) } ac.assert { canView(it.constitution, citizenOrNull) }
call.respond(it.constitution) call.respond(it.constitution)
} }
post<ConstitutionPaths.PostConstitutionRequest> { post<ConstitutionPaths.PostConstitutionRequest> {
it.getNewConstitution(call).let { constitution -> it.getNewConstitution(call).let { constitution ->
voter.assert { canCreate(constitution, citizenOrNull) } ac.assert { canCreate(constitution, citizenOrNull) }
repo.upsert(constitution) repo.upsert(constitution)
call.respond(constitution) call.respond(constitution)
} }

View File

@@ -1,13 +1,13 @@
package fr.dcproject.voter package fr.dcproject.security
/** Responses of voters */ /** Responses of AccessControl */
enum class Vote { enum class AccessDecision {
GRANTED, GRANTED,
DENIED; DENIED;
/** Helper to convert true/false to GRANTED/DENIED */ /** Helper to convert true/false to GRANTED/DENIED */
companion object { companion object {
fun toVote(lambda: () -> Boolean): Vote = when (lambda()) { fun toVote(lambda: () -> Boolean): AccessDecision = when (lambda()) {
true -> GRANTED true -> GRANTED
false -> DENIED false -> DENIED
} }
@@ -22,7 +22,7 @@ enum class Vote {
} }
} }
abstract class Voter { abstract class AccessControl {
/** /**
* A Shortcut for return a GrantedResponse * A Shortcut for return a GrantedResponse
*/ */
@@ -37,20 +37,20 @@ abstract class Voter {
* *
* If the list of responses is empty, return GRANTED * If the list of responses is empty, return GRANTED
*/ */
private fun VoterResponses.getOneResponse(): VoterResponse = this.firstOrNull { it.vote == Vote.DENIED } ?: granted() private fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: granted()
/** /**
* An helper to convert a list of subject into one response * An helper to convert a list of subject into one response
*/ */
protected fun <S : List<T>, T> canAll(items: S, action: (T) -> VoterResponse): VoterResponse = items protected fun <S : List<T>, T> canAll(items: S, action: (T) -> AccessResponse): AccessResponse = items
.map { action(it) } .map { action(it) }
.getOneResponse() .getOneResponse()
} }
/** /**
* Throw an Exception if voter return a DENIED response * Throw an Exception if AccessControl return a DENIED response
*/ */
fun <T : Voter> T.assert(action: T.() -> VoterResponse) { fun <T : AccessControl> T.assert(action: T.() -> AccessResponse) {
action().assert() action().assert()
} }
@@ -59,84 +59,84 @@ fun <T : Voter> T.assert(action: T.() -> VoterResponse) {
* *
* If the list of responses is empty, return GRANTED * If the list of responses is empty, return GRANTED
*/ */
fun VoterResponses.getOneResponse(): VoterResponse = this.firstOrNull { it.vote == Vote.DENIED } ?: GrantedResponse(first().voter) fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: GrantedResponse(first().accessControl)
/** /**
* Throw an Exception if one response is DENIED * Throw an Exception if one response is DENIED
*/ */
fun VoterResponses.assert() = this.getOneResponse().assert() fun AccessResponses.assert() = this.getOneResponse().assert()
class VoterDeniedException(private val voterResponses: VoterResponses) : Throwable(voterResponses.first().message) { class AccessDeniedException(private val accessResponses: AccessResponses) : Throwable(accessResponses.first().message) {
constructor(voterResponse: VoterResponse) : this(listOf(voterResponse)) constructor(accessResponse: AccessResponse) : this(listOf(accessResponse))
/** /**
* Get first response * Get first response
*/ */
fun first(): VoterResponse = voterResponses.first() fun first(): AccessResponse = accessResponses.first()
/** /**
* Check if the error code is present into the responses * Check if the error code is present into the responses
*/ */
fun hasErrorCode(code: String): Boolean = voterResponses fun hasErrorCode(code: String): Boolean = accessResponses
.filter { it.vote == Vote.DENIED } .filter { it.decision == AccessDecision.DENIED }
.any { it.code == code } .any { it.code == code }
/** /**
* Find and return the response than match with the error code * Find and return the response than match with the error code
*/ */
fun getErrorCode(code: String): VoterResponse? = voterResponses fun getErrorCode(code: String): AccessResponse? = accessResponses
.firstOrNull { it.vote == Vote.DENIED && it.code == code } .firstOrNull { it.decision == AccessDecision.DENIED && it.code == code }
/** /**
* Get a list of messages of all responses * Get a list of messages of all responses
*/ */
fun getMessages(): List<String> = voterResponses fun getMessages(): List<String> = accessResponses
.mapNotNull { it.message } .mapNotNull { it.message }
/** /**
* Get the first message * Get the first message
*/ */
fun getFirstMessage(): String? = voterResponses fun getFirstMessage(): String? = accessResponses
.first() .first()
.message .message
} }
/** /**
* The response that all Voter method return * The response that all AccessControl method return
* @see GrantedResponse * @see GrantedResponse
* @see DeniedResponse * @see DeniedResponse
*/ */
sealed class VoterResponse( sealed class AccessResponse(
val vote: Vote, val decision: AccessDecision,
val voter: Voter, val accessControl: AccessControl,
val message: String?, val message: String?,
val code: String? val code: String?
) { ) {
/** /**
* Convert response as boolean * Convert response as boolean
*/ */
fun toBoolean(): Boolean = vote.toBoolean() fun toBoolean(): Boolean = decision.toBoolean()
/** /**
* Throw Exception if response if DENIED * Throw Exception if response if DENIED
*/ */
fun assert() { fun assert() {
if (this.vote == Vote.DENIED) { if (this.decision == AccessDecision.DENIED) {
throw VoterDeniedException(this) throw AccessDeniedException(this)
} }
} }
} }
class GrantedResponse( class GrantedResponse(
voter: Voter, accessControl: AccessControl,
message: String? = null, message: String? = null,
code: String? = null code: String? = null
) : VoterResponse(Vote.GRANTED, voter, message, code) ) : AccessResponse(AccessDecision.GRANTED, accessControl, message, code)
class DeniedResponse( class DeniedResponse(
voter: Voter, accessControl: AccessControl,
message: String, message: String,
code: String code: String
) : VoterResponse(Vote.DENIED, voter, message, code) ) : AccessResponse(AccessDecision.DENIED, accessControl, message, code)
typealias VoterResponses = List<VoterResponse> typealias AccessResponses = List<AccessResponse>

View File

@@ -3,30 +3,30 @@ package fr.dcproject.security.voter
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.ConstitutionS import fr.dcproject.entity.ConstitutionS
import fr.dcproject.entity.ConstitutionSimple import fr.dcproject.entity.ConstitutionSimple
import fr.dcproject.voter.Voter import fr.dcproject.security.AccessControl
import fr.dcproject.voter.VoterResponse import fr.dcproject.security.AccessResponse
class ConstitutionVoter : Voter() { class ConstitutionAccessControl : AccessControl() {
fun canCreate(subject: ConstitutionS, citizen: CitizenI?): VoterResponse = when { fun canCreate(subject: ConstitutionS, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected to create constitution", "constitution.create.notConnected") citizen == null -> denied("You must be connected to create constitution", "constitution.create.notConnected")
else -> granted() else -> granted()
} }
fun <S : ConstitutionSimple<*, *>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse = fun <S : ConstitutionSimple<*, *>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
canAll(subjects) { canView(it, citizen) } canAll(subjects) { canView(it, citizen) }
fun canView(subject: ConstitutionSimple<*, *>, citizen: CitizenI?): VoterResponse = when { fun canView(subject: ConstitutionSimple<*, *>, citizen: CitizenI?): AccessResponse = when {
subject.isDeleted() -> denied("You cannot view a deleted constitution", "constitution.view.deleted") subject.isDeleted() -> denied("You cannot view a deleted constitution", "constitution.view.deleted")
else -> granted() else -> granted()
} }
fun canDelete(subject: ConstitutionSimple<*, *>, citizen: CitizenI?): VoterResponse = when { fun canDelete(subject: ConstitutionSimple<*, *>, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected to delete constitution", "constitution.delete.notConnected") citizen == null -> denied("You must be connected to delete constitution", "constitution.delete.notConnected")
subject.createdBy.id != citizen.id -> denied("You cannot delete the constitution of other citizen", "constitution.delete.otherCitizen") subject.createdBy.id != citizen.id -> denied("You cannot delete the constitution of other citizen", "constitution.delete.otherCitizen")
else -> granted() else -> granted()
} }
fun canUpdate(subject: ConstitutionSimple<*, *>, citizen: CitizenI?): VoterResponse = when { fun canUpdate(subject: ConstitutionSimple<*, *>, citizen: CitizenI?): AccessResponse = when {
citizen == null -> denied("You must be connected to update constitution", "constitution.update.notConnected") citizen == null -> denied("You must be connected to update constitution", "constitution.update.notConnected")
subject.createdBy.id != citizen.id -> denied("You cannot update the constitution of other citizen", "constitution.update.otherCitizen") subject.createdBy.id != citizen.id -> denied("You cannot update the constitution of other citizen", "constitution.update.otherCitizen")
else -> granted() else -> granted()

View File

@@ -1,13 +1,13 @@
package unit.voter package unit.security
import fr.dcproject.component.article.ArticleAccessControl
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleVoter
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.citizen.CitizenCart import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.GRANTED import fr.dcproject.security.AccessDecision.GRANTED
import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Paginated
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
@@ -23,8 +23,8 @@ import fr.dcproject.component.article.ArticleRepository as ArticleRepo
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class ArticleVoterTest { internal class ArticleAccessControlTest {
private val tesla = CitizenCart( private val tesla = CitizenCart(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"), id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User( user = User(
@@ -50,35 +50,35 @@ internal class ArticleVoterTest {
@Test @Test
fun `creator can be view the article`() { fun `creator can be view the article`() {
val article = getArticle(tesla).copy(draft = true) val article = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canView(article, tesla) .canView(article, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `other user can be view the article`() { fun `other user can be view the article`() {
val article = getArticle(tesla) val article = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canView(article, einstein) .canView(article, einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `other user can be view the article list`(): Unit = listOf(ArticleVoter(mockk())).run { fun `other user can be view the article list`(): Unit = listOf(ArticleAccessControl(mockk())).run {
val article = getArticle(tesla) val article = getArticle(tesla)
val article2 = getArticle(tesla) val article2 = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canView(listOf(article, article2), einstein) .canView(listOf(article, article2), einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `the no creator can not be view the article on draft`() { fun `the no creator can not be view the article on draft`() {
val article = getArticle(tesla).copy(draft = true) val article = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canView(article, einstein) .canView(article, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
@@ -86,31 +86,31 @@ internal class ArticleVoterTest {
val article = getArticle(tesla) val article = getArticle(tesla)
val article2 = getArticle(tesla).copy(draft = true) val article2 = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canView(listOf(article, article2), einstein) .canView(listOf(article, article2), einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can not view deleted article`() { fun `can not view deleted article`() {
val article = getArticle(tesla).copy(deletedAt = DateTime.now()) val article = getArticle(tesla).copy(deletedAt = DateTime.now())
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canView(article, tesla) .canView(article, tesla)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can delete article if owner`() { fun `can delete article if owner`() {
val article = getArticle(tesla) val article = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canDelete(article, tesla) .canDelete(article, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not delete article if not owner`() { fun `can not delete article if not owner`() {
val article = getArticle(tesla).copy(deletedAt = DateTime.now()) val article = getArticle(tesla).copy(deletedAt = DateTime.now())
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canDelete(article, einstein) .canDelete(article, einstein)
.code `should be` "article.delete.notYours" .code `should be` "article.delete.notYours"
} }
@@ -118,15 +118,15 @@ internal class ArticleVoterTest {
@Test @Test
fun `can create article if logged`() { fun `can create article if logged`() {
val article = getArticle(tesla) val article = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canUpsert(article, tesla) .canUpsert(article, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not create article if not logged`() { fun `can not create article if not logged`() {
val article = getArticle(tesla) val article = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canUpsert(article, null) .canUpsert(article, null)
.code `should be` "article.create.notConnected" .code `should be` "article.create.notConnected"
} }
@@ -134,15 +134,15 @@ internal class ArticleVoterTest {
@Test @Test
fun `can update article if yours`() { fun `can update article if yours`() {
val article = getArticle(tesla) val article = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canUpsert(article, tesla) .canUpsert(article, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not update article if not yours`() { fun `can not update article if not yours`() {
val article = getArticle(tesla) val article = getArticle(tesla)
ArticleVoter(getRepo(article)) ArticleAccessControl(getRepo(article))
.canUpsert(article, einstein) .canUpsert(article, einstein)
.code `should be` "article.update.notYours" .code `should be` "article.update.notYours"
} }

View File

@@ -1,12 +1,12 @@
package unit.voter package unit.security
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.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.CitizenBasic import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenI import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenVoter import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.GRANTED
import fr.dcproject.voter.Vote.GRANTED
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
@@ -17,8 +17,8 @@ import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class CitizenVoterTest { internal class CitizenAccessControlTest {
private val tesla = CitizenBasic( private val tesla = CitizenBasic(
user = User( user = User(
username = "nicolas-tesla", username = "nicolas-tesla",
@@ -51,50 +51,50 @@ internal class CitizenVoterTest {
@Test @Test
fun `can be view the citizen`() { fun `can be view the citizen`() {
CitizenVoter() CitizenAccessControl()
.canView(subject = einstein, connectedCitizen = tesla) .canView(subject = einstein, connectedCitizen = tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view the citizen list`() { fun `can be view the citizen list`() {
CitizenVoter() CitizenAccessControl()
.canView(subjects = listOf(tesla, einstein), connectedCitizen = einstein) .canView(subjects = listOf(tesla, einstein), connectedCitizen = einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not view deleted citizen`() { fun `can not view deleted citizen`() {
CitizenVoter() CitizenAccessControl()
.canView(subject = curie, connectedCitizen = tesla) .canView(subject = curie, connectedCitizen = tesla)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be update itself`() { fun `can be update itself`() {
CitizenVoter() CitizenAccessControl()
.canUpdate(subject = einstein, connectedCitizen = einstein) .canUpdate(subject = einstein, connectedCitizen = einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be update other citizen`() { fun `can not be update other citizen`() {
CitizenVoter() CitizenAccessControl()
.canUpdate(subject = tesla, connectedCitizen = einstein) .canUpdate(subject = tesla, connectedCitizen = einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be change password of itself`() { fun `can be change password of itself`() {
CitizenVoter() CitizenAccessControl()
.canChangePassword(subject = einstein, connectedCitizen = einstein) .canChangePassword(subject = einstein, connectedCitizen = einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be change password of other citizen`() { fun `can not be change password of other citizen`() {
CitizenVoter() CitizenAccessControl()
.canChangePassword(subject = tesla, connectedCitizen = einstein) .canChangePassword(subject = tesla, connectedCitizen = einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
} }

View File

@@ -1,4 +1,4 @@
package unit.voter package unit.security
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRef import fr.dcproject.component.article.ArticleRef
@@ -7,11 +7,11 @@ import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.Citizen import fr.dcproject.component.citizen.Citizen
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.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.CommentForUpdate import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.component.comment.generic.CommentForView import fr.dcproject.component.comment.generic.CommentForView
import fr.dcproject.component.comment.generic.CommentVoter import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.GRANTED
import fr.dcproject.voter.Vote.GRANTED
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
@@ -23,8 +23,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class CommentVoterTest { internal class CommentAccessControlTest {
private val tesla = Citizen( private val tesla = Citizen(
user = User( user = User(
username = "nicolas-tesla", username = "nicolas-tesla",
@@ -99,57 +99,57 @@ internal class CommentVoterTest {
@Test @Test
fun `can be view the comment`() { fun `can be view the comment`() {
CommentVoter() CommentAccessControl()
.canView(comment1, tesla) .canView(comment1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view the comment list`() { fun `can be view the comment list`() {
CommentVoter() CommentAccessControl()
.canView(listOf(comment1, comment2), einstein) .canView(listOf(comment1, comment2), einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be update your comment`() { fun `can be update your comment`() {
CommentVoter() CommentAccessControl()
.canUpdate(comment1, tesla) .canUpdate(comment1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be update other comment`() { fun `can not be update other comment`() {
CommentVoter() CommentAccessControl()
.canUpdate(comment1, einstein) .canUpdate(comment1, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be create a comment`() { fun `can be create a comment`() {
CommentVoter() CommentAccessControl()
.canCreate(comment1, tesla) .canCreate(comment1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be create a comment if target is deleted`() { fun `can not be create a comment if target is deleted`() {
CommentVoter() CommentAccessControl()
.canCreate(commentTargetDeleted, tesla) .canCreate(commentTargetDeleted, tesla)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can not be create a comment with other creator`() { fun `can not be create a comment with other creator`() {
CommentVoter() CommentAccessControl()
.canCreate(comment1, einstein) .canCreate(comment1, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can not be create a comment if not connected`() { fun `can not be create a comment if not connected`() {
CommentVoter() CommentAccessControl()
.canCreate(comment1, null) .canCreate(comment1, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
} }

View File

@@ -1,4 +1,4 @@
package unit.voter package unit.security
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.User import fr.dcproject.component.auth.User
@@ -8,9 +8,9 @@ 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.component.follow.Follow import fr.dcproject.component.follow.Follow
import fr.dcproject.component.follow.FollowVoter import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.GRANTED import fr.dcproject.security.AccessDecision.GRANTED
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
@@ -22,8 +22,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class FollowVoterTest { internal class FollowAccessControlTest {
private val tesla = CitizenBasic( private val tesla = CitizenBasic(
user = User( user = User(
username = "nicolas-tesla", username = "nicolas-tesla",
@@ -97,57 +97,57 @@ internal class FollowVoterTest {
@Test @Test
fun `can be view the follow`() { fun `can be view the follow`() {
FollowVoter() FollowAccessControl()
.canView(follow1, tesla2) .canView(follow1, tesla2)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view the follow list`() { fun `can be view the follow list`() {
FollowVoter() FollowAccessControl()
.canView(listOf(follow1), tesla2) .canView(listOf(follow1), tesla2)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view your anonymous follow`() { fun `can be view your anonymous follow`() {
FollowVoter() FollowAccessControl()
.canView(followAnon, einstein3) .canView(followAnon, einstein3)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be view the anonymous follow of other`() { fun `can not be view the anonymous follow of other`() {
FollowVoter() FollowAccessControl()
.canView(followAnon, tesla2) .canView(followAnon, tesla2)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be follow article`() { fun `can be follow article`() {
FollowVoter() FollowAccessControl()
.canCreate(follow1, tesla2) .canCreate(follow1, tesla2)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be follow article if not connected`() { fun `can not be follow article if not connected`() {
FollowVoter() FollowAccessControl()
.canCreate(follow1, null) .canCreate(follow1, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be unfollow article`() { fun `can be unfollow article`() {
FollowVoter() FollowAccessControl()
.canDelete(follow1, tesla2) .canDelete(follow1, tesla2)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be unfollow article if not connected`() { fun `can not be unfollow article if not connected`() {
FollowVoter() FollowAccessControl()
.canDelete(follow1, null) .canDelete(follow1, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
} }

View File

@@ -1,4 +1,4 @@
package unit.voter package unit.security
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.User import fr.dcproject.component.auth.User
@@ -6,11 +6,11 @@ import fr.dcproject.component.auth.UserI
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.component.opinion.OpinionVoter import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.entity.Opinion import fr.dcproject.component.opinion.entity.Opinion
import fr.dcproject.component.opinion.entity.OpinionChoice import fr.dcproject.component.opinion.entity.OpinionChoice
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.GRANTED import fr.dcproject.security.AccessDecision.GRANTED
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
@@ -22,8 +22,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class OpinionVoterTest { internal class OpinionAccessControlTest {
private val tesla = CitizenBasic( private val tesla = CitizenBasic(
user = User( user = User(
username = "nicolas-tesla", username = "nicolas-tesla",
@@ -74,50 +74,50 @@ internal class OpinionVoterTest {
@Test @Test
fun `can be view the opinion`() { fun `can be view the opinion`() {
OpinionVoter() OpinionAccessControl()
.canView(opinion1, tesla) .canView(opinion1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view the opinion list`() { fun `can be view the opinion list`() {
OpinionVoter() OpinionAccessControl()
.canView(listOf(opinion1), tesla) .canView(listOf(opinion1), tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be opinion an article`() { fun `can be opinion an article`() {
OpinionVoter() OpinionAccessControl()
.canCreate(opinion1, tesla) .canCreate(opinion1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be opinion if not connected`() { fun `can not be opinion if not connected`() {
OpinionVoter() OpinionAccessControl()
.canCreate(opinion1, null) .canCreate(opinion1, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be remove opinion`() { fun `can be remove opinion`() {
OpinionVoter() OpinionAccessControl()
.canDelete(opinion1, tesla) .canDelete(opinion1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be remove opinion if not connected`() { fun `can not be remove opinion if not connected`() {
OpinionVoter() OpinionAccessControl()
.canDelete(opinion1, null) .canDelete(opinion1, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can not be remove opinion of other user`() { fun `can not be remove opinion of other user`() {
OpinionVoter() OpinionAccessControl()
.canDelete(opinion1, einstein) .canDelete(opinion1, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
} }

View File

@@ -1,4 +1,4 @@
package unit.voter package unit.security
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.User import fr.dcproject.component.auth.User
@@ -6,9 +6,9 @@ import fr.dcproject.component.auth.UserI
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.component.opinion.OpinionChoiceVoter import fr.dcproject.component.opinion.OpinionChoiceAccessControl
import fr.dcproject.component.opinion.entity.OpinionChoice import fr.dcproject.component.opinion.entity.OpinionChoice
import fr.dcproject.voter.Vote.GRANTED import fr.dcproject.security.AccessDecision.GRANTED
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
@@ -20,8 +20,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class OpinionChoiceVoterTest { internal class OpinionChoiceAccessControlTest {
private val tesla = CitizenBasic( private val tesla = CitizenBasic(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"), id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User( user = User(
@@ -57,15 +57,15 @@ internal class OpinionChoiceVoterTest {
@Test @Test
fun `can be view the opinion choice`() { fun `can be view the opinion choice`() {
OpinionChoiceVoter() OpinionChoiceAccessControl()
.canView(choice1, tesla) .canView(choice1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view the opinion choice list`() { fun `can be view the opinion choice list`() {
OpinionChoiceVoter() OpinionChoiceAccessControl()
.canView(listOf(choice1), tesla) .canView(listOf(choice1), tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
} }

View File

@@ -1,4 +1,4 @@
package unit.voter package unit.security
import fr.dcproject.component.article.ArticleForView import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.auth.User import fr.dcproject.component.auth.User
@@ -7,10 +7,10 @@ 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.component.vote.VoteVoter import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.entity.VoteForUpdate import fr.dcproject.component.vote.entity.VoteForUpdate
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.GRANTED import fr.dcproject.security.AccessDecision.GRANTED
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
@@ -23,8 +23,8 @@ import fr.dcproject.component.vote.entity.Vote as VoteEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class VoteVoterTest { internal class VoteAccessControlTest {
private val tesla = Citizen( private val tesla = Citizen(
id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"), id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"),
user = User( user = User(
@@ -101,43 +101,43 @@ internal class VoteVoterTest {
@Test @Test
fun `can be view your the vote`() { fun `can be view your the vote`() {
VoteVoter() VoteAccessControl()
.canView(vote1, tesla) .canView(vote1, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be view vote of other`() { fun `can not be view vote of other`() {
VoteVoter() VoteAccessControl()
.canView(vote1, einstein) .canView(vote1, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be view your votes list`() { fun `can be view your votes list`() {
VoteVoter() VoteAccessControl()
.canView(listOf(vote1), tesla) .canView(listOf(vote1), tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be vote an article`() { fun `can be vote an article`() {
VoteVoter() VoteAccessControl()
.canCreate(voteForUpdate, tesla) .canCreate(voteForUpdate, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be vote if not connected`() { fun `can not be vote if not connected`() {
VoteVoter() VoteAccessControl()
.canCreate(voteForUpdate, null) .canCreate(voteForUpdate, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can not be vote an article if article is deleted`() { fun `can not be vote an article if article is deleted`() {
VoteVoter() VoteAccessControl()
.canCreate(voteOnDeleted, tesla) .canCreate(voteOnDeleted, tesla)
.vote `should be` DENIED .decision `should be` DENIED
} }
} }

View File

@@ -1,14 +1,14 @@
package unit.voter package unit.security
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.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.component.workgroup.WorkgroupVoter import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.WorkgroupWithMembersI import fr.dcproject.component.workgroup.WorkgroupWithMembersI
import fr.dcproject.voter.Vote.DENIED import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.voter.Vote.GRANTED import fr.dcproject.security.AccessDecision.GRANTED
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
@@ -21,8 +21,8 @@ import fr.dcproject.component.workgroup.Workgroup as WorkgroupEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT) @Execution(CONCURRENT)
@Tag("voter") @Tag("security")
internal class WorkgroupVoterTest { internal class WorkgroupAccessControlTest {
private val tesla = CitizenBasic( private val tesla = CitizenBasic(
user = User( user = User(
username = "nicolas-tesla", username = "nicolas-tesla",
@@ -73,78 +73,78 @@ internal class WorkgroupVoterTest {
@Test @Test
fun `can be view your workgroup`() { fun `can be view your workgroup`() {
WorkgroupVoter() WorkgroupAccessControl()
.canView(workgroupPublic, tesla) .canView(workgroupPublic, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view your workgroup if is not public`() { fun `can be view your workgroup if is not public`() {
WorkgroupVoter() WorkgroupAccessControl()
.canView(workgroupAnon, tesla) .canView(workgroupAnon, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be view workgroup of other if is public`() { fun `can be view workgroup of other if is public`() {
WorkgroupVoter() WorkgroupAccessControl()
.canView(workgroupPublic, einstein) .canView(workgroupPublic, einstein)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be view workgroup of other if is not public`() { fun `can not be view workgroup of other if is not public`() {
WorkgroupVoter() WorkgroupAccessControl()
.canView(workgroupAnon, einstein) .canView(workgroupAnon, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be view your workgroup list`() { fun `can be view your workgroup list`() {
WorkgroupVoter() WorkgroupAccessControl()
.canView(listOf(workgroupPublic, workgroupAnon), tesla) .canView(listOf(workgroupPublic, workgroupAnon), tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can be create workgroup`() { fun `can be create workgroup`() {
WorkgroupVoter() WorkgroupAccessControl()
.canCreate(workgroupPublic, tesla) .canCreate(workgroupPublic, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be create workgroup if not connected`() { fun `can not be create workgroup if not connected`() {
WorkgroupVoter() WorkgroupAccessControl()
.canCreate(workgroupPublic, null) .canCreate(workgroupPublic, null)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be delete workgroup if owner`() { fun `can be delete workgroup if owner`() {
WorkgroupVoter() WorkgroupAccessControl()
.canDelete(workgroupPublic, tesla) .canDelete(workgroupPublic, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be delete workgroup if not owner`() { fun `can not be delete workgroup if not owner`() {
WorkgroupVoter() WorkgroupAccessControl()
.canDelete(workgroupPublic, einstein) .canDelete(workgroupPublic, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
@Test @Test
fun `can be update workgroup if owner`() { fun `can be update workgroup if owner`() {
WorkgroupVoter() WorkgroupAccessControl()
.canUpdate(workgroupPublic, tesla) .canUpdate(workgroupPublic, tesla)
.vote `should be` GRANTED .decision `should be` GRANTED
} }
@Test @Test
fun `can not be update workgroup if not owner`() { fun `can not be update workgroup if not owner`() {
WorkgroupVoter() WorkgroupAccessControl()
.canUpdate(workgroupPublic, einstein) .canUpdate(workgroupPublic, einstein)
.vote `should be` DENIED .decision `should be` DENIED
} }
} }