Rename Voter to AccessControl
This commit is contained in:
@@ -28,7 +28,7 @@ import fr.dcproject.routes.commentConstitution
|
||||
import fr.dcproject.routes.constitution
|
||||
import fr.dcproject.routes.definition
|
||||
import fr.dcproject.routes.notificationArticle
|
||||
import fr.dcproject.voter.VoterDeniedException
|
||||
import fr.dcproject.security.AccessDeniedException
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
@@ -166,7 +166,7 @@ fun Application.module(env: Env = PROD) {
|
||||
exception<NotFoundException> { e ->
|
||||
call.respond(HttpStatusCode.NotFound, e.message!!)
|
||||
}
|
||||
exception<VoterDeniedException> {
|
||||
exception<AccessDeniedException> {
|
||||
if (call.user == null) call.respond(HttpStatusCode.Unauthorized)
|
||||
else call.respond(HttpStatusCode.Forbidden)
|
||||
}
|
||||
|
||||
@@ -8,31 +8,31 @@ import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.ArticleRepository
|
||||
import fr.dcproject.component.article.ArticleViewManager
|
||||
import fr.dcproject.component.article.ArticleVoter
|
||||
import fr.dcproject.component.auth.PasswordlessAuth
|
||||
import fr.dcproject.component.auth.UserRepository
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.component.citizen.CitizenRepository
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.generic.CommentVoter
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
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.OpinionChoiceVoter
|
||||
import fr.dcproject.component.opinion.OpinionVoter
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.VoteArticleRepository
|
||||
import fr.dcproject.component.vote.VoteCommentRepository
|
||||
import fr.dcproject.component.vote.VoteConstitutionRepository
|
||||
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.WorkgroupVoter
|
||||
import fr.dcproject.event.publisher.Publisher
|
||||
import fr.dcproject.messages.Mailer
|
||||
import fr.dcproject.messages.NotificationEmailSender
|
||||
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.Requester
|
||||
import fr.postgresjson.migration.Migrations
|
||||
@@ -125,16 +125,16 @@ val KoinModule = module {
|
||||
single { OpinionArticleRepository(get()) }
|
||||
single { WorkgroupRepository(get()) }
|
||||
|
||||
// Voters
|
||||
single { ArticleVoter(get()) }
|
||||
single { CitizenVoter() }
|
||||
single { CommentVoter() }
|
||||
single { WorkgroupVoter() }
|
||||
single { ConstitutionVoter() }
|
||||
single { VoteVoter() }
|
||||
single { FollowVoter() }
|
||||
single { OpinionVoter() }
|
||||
single { OpinionChoiceVoter() }
|
||||
// AccessControl
|
||||
single { ArticleAccessControl(get()) }
|
||||
single { CitizenAccessControl() }
|
||||
single { CommentAccessControl() }
|
||||
single { WorkgroupAccessControl() }
|
||||
single { ConstitutionAccessControl() }
|
||||
single { VoteAccessControl() }
|
||||
single { FollowAccessControl() }
|
||||
single { OpinionAccessControl() }
|
||||
single { OpinionChoiceAccessControl() }
|
||||
|
||||
// Elasticsearch Client
|
||||
single<RestClient> {
|
||||
|
||||
@@ -3,20 +3,20 @@ package fr.dcproject.component.article
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.entity.CreatedBy
|
||||
import fr.dcproject.entity.VersionableRef
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
|
||||
class ArticleVoter(private val articleRepo: ArticleRepository) : Voter() {
|
||||
fun <S : ArticleAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse =
|
||||
class ArticleAccessControl(private val articleRepo: ArticleRepository) : AccessControl() {
|
||||
fun <S : ArticleAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
|
||||
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")
|
||||
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()
|
||||
}
|
||||
|
||||
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")
|
||||
return if (subject.createdBy.id == citizen.id) {
|
||||
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,
|
||||
S : CreatedBy<*>,
|
||||
S : VersionableRef {
|
||||
@@ -1,10 +1,10 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.article.ArticleRepository
|
||||
import fr.dcproject.component.article.ArticleVoter
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -31,10 +31,10 @@ object FindArticleVersions {
|
||||
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
|
||||
findVersionsByVersionId(request.page, request.limit, request.article.versionId)
|
||||
|
||||
fun Route.findArticleVersions(repo: ArticleRepository, voter: ArticleVoter) {
|
||||
fun Route.findArticleVersions(repo: ArticleRepository, ac: ArticleAccessControl) {
|
||||
get<ArticleVersionsRequest> {
|
||||
repo.findVersions(it)
|
||||
.apply { voter.assert { canView(it.article, citizenOrNull) } }
|
||||
.apply { ac.assert { canView(it.article, citizenOrNull) } }
|
||||
.let { call.respond(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.ArticleForListing
|
||||
import fr.dcproject.component.article.ArticleRepository
|
||||
import fr.dcproject.component.article.ArticleVoter
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
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> {
|
||||
repo.findArticles(it)
|
||||
.apply { voter.assert { canView(result, citizenOrNull) } }
|
||||
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||
.let { call.respond(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.article.ArticleRepository
|
||||
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.auth.citizenOrNull
|
||||
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.Versionable
|
||||
import fr.dcproject.dto.Viewable
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
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> {
|
||||
voter.assert { canView(it.article, citizenOrNull) }
|
||||
ac.assert { canView(it.article, citizenOrNull) }
|
||||
|
||||
Output(
|
||||
it.article,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.ArticleForUpdate
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
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.auth.citizen
|
||||
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.event.ArticleUpdate
|
||||
import fr.dcproject.event.raiseEvent
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
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 {
|
||||
ArticleForUpdate(
|
||||
id = id ?: UUID.randomUUID(),
|
||||
@@ -57,7 +57,7 @@ object UpsertArticle {
|
||||
|
||||
post<UpsertArticleRequest> {
|
||||
val article = call.convertRequestToEntity()
|
||||
voter.assert { canUpsert(article, citizenOrNull) }
|
||||
ac.assert { canUpsert(article, citizenOrNull) }
|
||||
val newArticle: ArticleForView = repo.upsert(article) ?: error("Article not updated")
|
||||
call.respond(newArticle)
|
||||
raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle))
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
package fr.dcproject.component.citizen
|
||||
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
import fr.postgresjson.entity.EntityDeletedAt
|
||||
|
||||
class CitizenVoter : Voter() {
|
||||
fun <S> canView(subjects: List<S>, connectedCitizen: CitizenI?): VoterResponse where S : CitizenI, S : EntityDeletedAt =
|
||||
class CitizenAccessControl : AccessControl() {
|
||||
fun <S> canView(subjects: List<S>, connectedCitizen: CitizenI?): AccessResponse where S : CitizenI, S : EntityDeletedAt =
|
||||
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")
|
||||
return if (subject.isDeleted()) denied("You cannot view a deleted citizen", "citizen.view.deleted")
|
||||
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")
|
||||
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")
|
||||
return if (subject.id == connectedCitizen.id) granted() else denied("You can only change your password", "citizen.password.notYours")
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import fr.dcproject.component.auth.UserRepository
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.UserPasswordCredential
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -24,9 +24,9 @@ object ChangeMyPassword {
|
||||
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> {
|
||||
voter.assert { canChangePassword(it.citizen, citizenOrNull) }
|
||||
ac.assert { canChangePassword(it.citizen, citizenOrNull) }
|
||||
try {
|
||||
val content = call.receive<ChangePasswordCitizenRequest.Input>()
|
||||
val currentUser = userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.component.citizen.CitizenRepository
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -25,10 +25,10 @@ object FindCitizens {
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
|
||||
fun Route.findCitizen(voter: CitizenVoter, repo: CitizenRepository) {
|
||||
fun Route.findCitizen(ac: CitizenAccessControl, repo: CitizenRepository) {
|
||||
get<CitizensRequest> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -17,13 +17,13 @@ object GetCurrentCitizen {
|
||||
@Location("/citizens/current")
|
||||
class CurrentCitizenRequest
|
||||
|
||||
fun Route.getCurrentCitizen(voter: CitizenVoter) {
|
||||
fun Route.getCurrentCitizen(ac: CitizenAccessControl) {
|
||||
get<CurrentCitizenRequest> {
|
||||
val currentUser = citizenOrNull
|
||||
if (currentUser === null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
voter.assert { canView(currentUser, citizenOrNull) }
|
||||
ac.assert { canView(currentUser, citizenOrNull) }
|
||||
call.respond(citizen)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -16,9 +16,9 @@ object GetOneCitizen {
|
||||
@Location("/citizens/{citizen}")
|
||||
class CitizenRequest(val citizen: Citizen)
|
||||
|
||||
fun Route.getOneCitizen(voter: CitizenVoter) {
|
||||
fun Route.getOneCitizen(ac: CitizenAccessControl) {
|
||||
get<CitizenRequest> {
|
||||
voter.assert { canView(it.citizen, citizenOrNull) }
|
||||
ac.assert { canView(it.citizen, citizenOrNull) }
|
||||
|
||||
call.respond(it.citizen)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
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.CommentVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
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> {
|
||||
it.getComment(call).let { comment ->
|
||||
voter.assert { canCreate(comment, citizenOrNull) }
|
||||
ac.assert { canCreate(comment, citizenOrNull) }
|
||||
repo.comment(comment)
|
||||
call.respond(HttpStatusCode.Created, comment)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package fr.dcproject.component.comment.article.routes
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
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.PaginatedRequestI
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -28,11 +28,11 @@ object GetArticleComments {
|
||||
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> {
|
||||
val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort)
|
||||
if (comment.result.isNotEmpty()) {
|
||||
voter.assert { canView(comment.result, citizenOrNull) }
|
||||
ac.assert { canView(comment.result, citizenOrNull) }
|
||||
}
|
||||
call.respond(HttpStatusCode.OK, comment)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package fr.dcproject.component.comment.article.routes
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.comment.article.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.generic.CommentVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -17,10 +17,10 @@ object GetCitizenArticleComments {
|
||||
@Location("/citizens/{citizen}/comments/articles")
|
||||
class CitizenCommentArticleRequest(val citizen: Citizen)
|
||||
|
||||
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, voter: CommentVoter) {
|
||||
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
get<CitizenCommentArticleRequest> {
|
||||
repo.findByCitizen(it.citizen).let { comments ->
|
||||
voter.assert { canView(comments.result, citizenOrNull) }
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(comments)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@ package fr.dcproject.component.comment.generic
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.entity.HasTarget
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
import fr.postgresjson.entity.EntityCreatedBy
|
||||
import fr.postgresjson.entity.EntityDeletedAt
|
||||
|
||||
class CommentVoter : Voter() {
|
||||
fun <S> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse
|
||||
class CommentAccessControl : AccessControl() {
|
||||
fun <S> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse
|
||||
where S : CommentI,
|
||||
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,
|
||||
S : EntityDeletedAt = when {
|
||||
subject.isDeleted() -> denied("Your cannot view a deleted comment", "comment.view.deleted")
|
||||
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,
|
||||
S : EntityCreatedBy<CR>,
|
||||
S : CommentWithParentI<*>,
|
||||
@@ -31,7 +31,7 @@ class CommentVoter : Voter() {
|
||||
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,
|
||||
S : EntityCreatedBy<CR> = when {
|
||||
citizen == null -> denied("You must be connected to update comment", "comment.update.notConnected")
|
||||
@@ -2,11 +2,11 @@ package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.CommentRef
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import fr.dcproject.component.comment.generic.CommentVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -24,7 +24,7 @@ object CreateCommentChildren {
|
||||
class Input(val content: String)
|
||||
}
|
||||
|
||||
fun Route.createCommentChildren(repo: CommentRepository, voter: CommentVoter) {
|
||||
fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
post<CreateCommentChildrenRequest> {
|
||||
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
val newComment = CommentForUpdate(
|
||||
@@ -33,7 +33,7 @@ object CreateCommentChildren {
|
||||
parent = parent
|
||||
)
|
||||
|
||||
voter.assert { canCreate(newComment, citizenOrNull) }
|
||||
ac.assert { canCreate(newComment, citizenOrNull) }
|
||||
repo.comment(newComment)
|
||||
|
||||
call.respond(HttpStatusCode.Created, newComment)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
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.CommentRepository
|
||||
import fr.dcproject.component.comment.generic.CommentVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -20,10 +20,10 @@ object EditComment {
|
||||
@Location("/comments/{comment}")
|
||||
class EditCommentRequest(val comment: CommentRef)
|
||||
|
||||
fun Route.editComment(repo: CommentRepository, voter: CommentVoter) {
|
||||
fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
put<EditCommentRequest> {
|
||||
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()
|
||||
repo.edit(comment)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
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.CommentVoter
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -25,7 +25,7 @@ object GetCommentChildren {
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
|
||||
fun Route.getChildrenComments(repo: CommentRepository, voter: CommentVoter) {
|
||||
fun Route.getChildrenComments(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
get<CommentChildrenRequest> {
|
||||
val comments =
|
||||
repo.findByParent(
|
||||
@@ -34,7 +34,7 @@ object GetCommentChildren {
|
||||
it.limit
|
||||
)
|
||||
|
||||
voter.assert { canView(comments.result, citizenOrNull) }
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
|
||||
call.respond(HttpStatusCode.OK, comments)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
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.CommentRepository
|
||||
import fr.dcproject.component.comment.generic.CommentVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -19,10 +19,10 @@ object GetOneComment {
|
||||
@Location("/comments/{comment}")
|
||||
class CommentRequest(val comment: CommentRef)
|
||||
|
||||
fun Route.getOneComment(repo: CommentRepository, voter: CommentVoter) {
|
||||
fun Route.getOneComment(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
get<CommentRequest> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
package fr.dcproject.component.follow
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
import fr.dcproject.component.follow.Follow as FollowEntity
|
||||
|
||||
class FollowVoter : Voter() {
|
||||
fun canCreate(subject: FollowI, citizen: CitizenI?): VoterResponse {
|
||||
class FollowAccessControl : AccessControl() {
|
||||
fun canCreate(subject: FollowI, citizen: CitizenI?): AccessResponse {
|
||||
return if (citizen == null) denied("You must be connected to follow", "follow.create.notConnected")
|
||||
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")
|
||||
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) }
|
||||
|
||||
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()
|
||||
else denied("You cannot view an anonymous follow", "follow.view.anonymous")
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package fr.dcproject.component.follow.routes.article
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -20,10 +20,10 @@ object FollowArticle {
|
||||
@Location("/articles/{article}/follows")
|
||||
class ArticleFollowRequest(val article: ArticleRef)
|
||||
|
||||
fun Route.followArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
fun Route.followArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
post<ArticleFollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||
voter.assert { canCreate(follow, citizenOrNull) }
|
||||
ac.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package fr.dcproject.component.follow.routes.article
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -19,10 +19,10 @@ object GetFollowArticle {
|
||||
@Location("/articles/{article}/follows")
|
||||
class ArticleFollowRequest(val article: ArticleRef)
|
||||
|
||||
fun Route.getFollowArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
fun Route.getFollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
get<ArticleFollowRequest> {
|
||||
repo.findFollow(citizen, it.article)?.let { follow ->
|
||||
voter.assert { canView(follow, citizenOrNull) }
|
||||
ac.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(follow)
|
||||
} ?: call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package fr.dcproject.component.follow.routes.article
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -17,10 +17,10 @@ object GetMyFollowsArticle {
|
||||
@Location("/citizens/{citizen}/follows/articles")
|
||||
class CitizenFollowArticleRequest(val citizen: Citizen)
|
||||
|
||||
fun Route.getMyFollowsArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
fun Route.getMyFollowsArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
get<CitizenFollowArticleRequest> {
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
voter.assert { canView(follows.result, citizenOrNull) }
|
||||
ac.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(follows)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package fr.dcproject.component.follow.routes.article
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -20,10 +20,10 @@ object UnfollowArticle {
|
||||
@Location("/articles/{article}/follows")
|
||||
class ArticleFollowRequest(val article: ArticleRef)
|
||||
|
||||
fun Route.unfollowArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
fun Route.unfollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
delete<ArticleFollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||
voter.assert { canDelete(follow, citizenOrNull) }
|
||||
ac.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -20,10 +20,10 @@ object FollowConstitution {
|
||||
@Location("/constitutions/{constitution}/follows")
|
||||
class ConstitutionFollowRequest(val constitution: ConstitutionRef)
|
||||
|
||||
fun Route.followConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
fun Route.followConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
post<ConstitutionFollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||
voter.assert { canCreate(follow, citizenOrNull) }
|
||||
ac.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -19,10 +19,10 @@ object GetFollowConstitution {
|
||||
@Location("/constitutions/{constitution}/follows")
|
||||
class ConstitutionFollowRequest(val constitution: ConstitutionRef)
|
||||
|
||||
fun Route.getFollowConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
fun Route.getFollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
get<ConstitutionFollowRequest> {
|
||||
repo.findFollow(citizen, it.constitution)?.let { follow ->
|
||||
voter.assert { canView(follow, citizenOrNull) }
|
||||
ac.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(follow)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -17,10 +17,10 @@ object GetMyFollowsConstitution {
|
||||
@Location("/citizens/{citizen}/follows/constitutions")
|
||||
class CitizenFollowConstitutionRequest(val citizen: CitizenRef)
|
||||
|
||||
fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
get<CitizenFollowConstitutionRequest> {
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
voter.assert { canView(follows.result, citizenOrNull) }
|
||||
ac.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(follows)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -20,10 +20,10 @@ object UnfollowConstitution {
|
||||
@Location("/constitutions/{constitution}/follows")
|
||||
class ConstitutionUnfollowRequest(val constitution: ConstitutionRef)
|
||||
|
||||
fun Route.unfollowConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
fun Route.unfollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
delete<ConstitutionUnfollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||
voter.assert { canDelete(follow, citizenOrNull) }
|
||||
ac.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@ package fr.dcproject.component.opinion
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.opinion.entity.OpinionI
|
||||
import fr.dcproject.entity.HasTarget
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
import fr.postgresjson.entity.EntityCreatedBy
|
||||
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) }
|
||||
|
||||
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
|
||||
return when {
|
||||
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) }
|
||||
|
||||
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")
|
||||
subject.createdBy.id != citizen.id -> denied("You can only delete your opinions", "opinion.delete.notYours")
|
||||
else -> granted()
|
||||
@@ -2,14 +2,14 @@ package fr.dcproject.component.opinion
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.opinion.entity.OpinionChoice
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
|
||||
class OpinionChoiceVoter : Voter() {
|
||||
fun canView(subjects: List<OpinionChoice>, citizen: CitizenI?): VoterResponse =
|
||||
class OpinionChoiceAccessControl : AccessControl() {
|
||||
fun canView(subjects: List<OpinionChoice>, citizen: CitizenI?): AccessResponse =
|
||||
canAll(subjects) { canView(it, citizen) }
|
||||
|
||||
fun canView(subject: OpinionChoice, citizen: CitizenI?): VoterResponse {
|
||||
fun canView(subject: OpinionChoice, citizen: CitizenI?): AccessResponse {
|
||||
return granted()
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@ package fr.dcproject.component.opinion.routes
|
||||
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
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.security.assert
|
||||
import fr.dcproject.utils.toUUID
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -27,10 +27,10 @@ object GetCitizenOpinions {
|
||||
val id: List<UUID> = id.toUUID()
|
||||
}
|
||||
|
||||
fun Route.getCitizenOpinions(repo: OpinionArticleRepository, voter: OpinionVoter) {
|
||||
fun Route.getCitizenOpinions(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
get<CitizenOpinions> {
|
||||
val opinionsEntities: List<Opinion<ArticleRef>> = repo.findCitizenOpinionsByTargets(it.citizen, it.id)
|
||||
voter.assert { canView(opinionsEntities, citizenOrNull) }
|
||||
ac.assert { canView(opinionsEntities, citizenOrNull) }
|
||||
|
||||
call.respond(opinionsEntities)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package fr.dcproject.component.opinion.routes
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
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.PaginatedRequestI
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -27,10 +27,10 @@ object GetMyOpinionsArticle {
|
||||
limit: Int = 50
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
|
||||
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, voter: OpinionVoter) {
|
||||
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
get<CitizenOpinionsArticleRequest> {
|
||||
val opinions = repo.findCitizenOpinions(citizen, it.page, it.limit)
|
||||
voter.assert { canView(opinions.result, citizenOrNull) }
|
||||
ac.assert { canView(opinions.result, citizenOrNull) }
|
||||
call.respond(opinions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.opinion.routes
|
||||
|
||||
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.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -16,9 +16,9 @@ object GetOpinionChoice {
|
||||
@Location("/opinions/{opinionChoice}")
|
||||
class OpinionChoiceRequest(val opinionChoice: OpinionChoice)
|
||||
|
||||
fun Route.getOpinionChoice(voter: OpinionChoiceVoter) {
|
||||
fun Route.getOpinionChoice(ac: OpinionChoiceAccessControl) {
|
||||
get<OpinionChoiceRequest> {
|
||||
voter.assert { canView(it.opinionChoice, citizenOrNull) }
|
||||
ac.assert { canView(it.opinionChoice, citizenOrNull) }
|
||||
|
||||
call.respond(it.opinionChoice)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.opinion.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.opinion.OpinionChoiceAccessControl
|
||||
import fr.dcproject.component.opinion.OpinionChoiceRepository
|
||||
import fr.dcproject.component.opinion.OpinionChoiceVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -16,10 +16,10 @@ object GetOpinionChoices {
|
||||
@Location("/opinions")
|
||||
class OpinionChoicesRequest(val targets: List<String> = emptyList())
|
||||
|
||||
fun Route.getOpinionChoices(repo: OpinionChoiceRepository, voter: OpinionChoiceVoter) {
|
||||
fun Route.getOpinionChoices(repo: OpinionChoiceRepository, ac: OpinionChoiceAccessControl) {
|
||||
get<OpinionChoicesRequest> {
|
||||
val opinionChoices = repo.findOpinionsChoices(it.targets)
|
||||
voter.assert { canView(opinionChoices, citizenOrNull) }
|
||||
ac.assert { canView(opinionChoices, citizenOrNull) }
|
||||
|
||||
call.respond(opinionChoices)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package fr.dcproject.component.opinion.routes
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.OpinionForUpdate
|
||||
import fr.dcproject.security.assert
|
||||
import fr.dcproject.utils.toUUID
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
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> {
|
||||
call.receive<ArticleOpinion.Body>().ids.map { id ->
|
||||
OpinionForUpdate(
|
||||
@@ -40,7 +40,7 @@ object OpinionArticle {
|
||||
createdBy = citizen
|
||||
)
|
||||
}.let { opinions ->
|
||||
voter.assert { canCreate(opinions, citizenOrNull) }
|
||||
ac.assert { canCreate(opinions, citizenOrNull) }
|
||||
repo.updateOpinions(opinions)
|
||||
}.let {
|
||||
call.respond(HttpStatusCode.Created, it)
|
||||
|
||||
@@ -3,22 +3,22 @@ package fr.dcproject.component.vote
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.vote.entity.VoteForUpdateI
|
||||
import fr.dcproject.entity.TargetI
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
import fr.postgresjson.entity.EntityDeletedAt
|
||||
import fr.dcproject.component.vote.entity.Vote as VoteEntity
|
||||
|
||||
class VoteVoter : Voter() {
|
||||
fun <S> canCreate(subject: VoteForUpdateI<S, *>, citizen: CitizenI?): VoterResponse where S : EntityDeletedAt, S : TargetI = when {
|
||||
class VoteAccessControl : AccessControl() {
|
||||
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")
|
||||
subject.target.isDeleted() -> denied("You cannot vote on deleted target", "vote.create.isDeleted")
|
||||
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) }
|
||||
|
||||
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")
|
||||
subject.createdBy.id != citizen.id -> denied("You can only display your votes", "vote.view.onlyYours")
|
||||
else -> granted()
|
||||
@@ -2,10 +2,10 @@ package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
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.voter.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -21,11 +21,11 @@ object GetCitizenVotes {
|
||||
val id: List<UUID> = id.toUUID()
|
||||
}
|
||||
|
||||
fun Route.getCitizenVote(repo: VoteRepository, voter: VoteVoter) {
|
||||
fun Route.getCitizenVote(repo: VoteRepository, ac: VoteAccessControl) {
|
||||
get<CitizenVotesRequest> {
|
||||
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
|
||||
if (votes.isNotEmpty()) {
|
||||
voter.assert { canView(votes, citizenOrNull) }
|
||||
ac.assert { canView(votes, citizenOrNull) }
|
||||
}
|
||||
call.respond(votes)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.VoteArticleRepository
|
||||
import fr.dcproject.component.vote.VoteVoter
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -24,10 +24,10 @@ object GetCitizenVotesOnArticle {
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
|
||||
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, voter: VoteVoter) {
|
||||
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
|
||||
get<CitizenVoteArticleRequest> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package fr.dcproject.component.vote.routes
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.VoteArticleRepository
|
||||
import fr.dcproject.component.vote.VoteVoter
|
||||
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.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -23,7 +23,7 @@ object PutVoteOnArticle {
|
||||
data class Content(var note: Int)
|
||||
}
|
||||
|
||||
fun Route.putVoteOnArticle(repo: VoteArticleRepository, voter: VoteVoter) {
|
||||
fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
|
||||
put<ArticleVoteRequest> {
|
||||
val content = call.receive<ArticleVoteRequest.Content>()
|
||||
val vote = VoteForUpdate(
|
||||
@@ -31,7 +31,7 @@ object PutVoteOnArticle {
|
||||
note = content.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
voter.assert { canCreate(vote, citizenOrNull) }
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
val votes = repo.vote(vote)
|
||||
call.respond(HttpStatusCode.Created, votes)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package fr.dcproject.component.vote.routes
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentRepository
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.VoteCommentRepository
|
||||
import fr.dcproject.component.vote.VoteVoter
|
||||
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.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -24,7 +24,7 @@ object PutVoteOnComment {
|
||||
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> {
|
||||
val comment = commentRepo.findById(it.comment)!!
|
||||
val content = call.receive<CommentVoteRequest.Content>()
|
||||
@@ -33,7 +33,7 @@ object PutVoteOnComment {
|
||||
note = content.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
voter.assert { canCreate(vote, citizenOrNull) }
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
val votes = voteCommentRepo.vote(vote)
|
||||
call.respond(HttpStatusCode.Created, votes)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.VoteConstitutionRepository
|
||||
import fr.dcproject.component.vote.VoteVoter
|
||||
import fr.dcproject.component.vote.entity.VoteForUpdate
|
||||
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.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -24,7 +24,7 @@ object VoteConstitution {
|
||||
data class Input(var note: Int)
|
||||
}
|
||||
|
||||
fun Route.voteConstitution(repo: VoteConstitutionRepository, voter: VoteVoter) {
|
||||
fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl) {
|
||||
put<ConstitutionVoteRequest> {
|
||||
val content = call.receive<Input>()
|
||||
val vote = VoteForUpdate(
|
||||
@@ -32,7 +32,7 @@ object VoteConstitution {
|
||||
note = content.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
voter.assert { canCreate(vote, citizenOrNull) }
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
repo.vote(vote)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
|
||||
@@ -2,48 +2,48 @@ package fr.dcproject.component.workgroup
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.workgroup.WorkgroupWithMembersI.Member.Role
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
|
||||
class WorkgroupVoter : Voter() {
|
||||
fun canCreate(subject: WorkgroupI, citizen: CitizenI?): VoterResponse {
|
||||
class WorkgroupAccessControl : AccessControl() {
|
||||
fun canCreate(subject: WorkgroupI, citizen: CitizenI?): AccessResponse {
|
||||
if (citizen == null) return denied("You must be connected to create workgroup", "workgroup.create.notConnected")
|
||||
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) }
|
||||
|
||||
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")
|
||||
else if (!subject.anonymous) granted()
|
||||
else if (subject.anonymous && citizen != null && subject.isMember(citizen)) granted()
|
||||
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")
|
||||
return if (subject.hasRole(Role.MASTER, citizen)) granted()
|
||||
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")
|
||||
return if (subject.hasRole(Role.MASTER, citizen)) granted()
|
||||
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")
|
||||
subject.hasRole(Role.MASTER, citizen) -> granted()
|
||||
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")
|
||||
subject.hasRole(Role.MASTER, citizen) -> granted()
|
||||
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")
|
||||
subject.hasRole(Role.MASTER, citizen) -> granted()
|
||||
else -> denied("You must have MASTER Role for remove members of workgroup", "workgroup.removeMember.role")
|
||||
@@ -2,11 +2,11 @@ package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupSimple
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
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.http.HttpStatusCode
|
||||
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> {
|
||||
call.receive<Input>().run {
|
||||
WorkgroupSimple(
|
||||
@@ -42,7 +42,7 @@ object CreateWorkgroup {
|
||||
citizen
|
||||
)
|
||||
}.let { workgroup ->
|
||||
voter.assert { canCreate(workgroup, citizenOrNull) }
|
||||
ac.assert { canCreate(workgroup, citizenOrNull) }
|
||||
repo.upsert(workgroup)
|
||||
}.let {
|
||||
call.respond(HttpStatusCode.Created, it)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -18,10 +18,10 @@ object DeleteWorkgroup {
|
||||
@Location("/workgroups/{workgroupId}")
|
||||
class DeleteWorkgroupRequest(val workgroupId: UUID)
|
||||
|
||||
fun Route.deleteWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) {
|
||||
fun Route.deleteWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
delete<DeleteWorkgroupRequest> {
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
voter.assert { canDelete(workgroup, citizenOrNull) }
|
||||
ac.assert { canDelete(workgroup, citizenOrNull) }
|
||||
repo.delete(workgroup)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
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.http.HttpStatusCode
|
||||
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> {
|
||||
repo.findById(it.workgroupId)?.let { old ->
|
||||
call.receive<Input>().run {
|
||||
@@ -38,7 +38,7 @@ object EditWorkgroup {
|
||||
logo = logo ?: old.logo,
|
||||
anonymous = anonymous ?: old.anonymous
|
||||
).let { workgroup ->
|
||||
voter.assert { canUpdate(workgroup, citizenOrNull) }
|
||||
ac.assert { canUpdate(workgroup, citizenOrNull) }
|
||||
repo.upsert(workgroup)
|
||||
call.respond(HttpStatusCode.OK, it)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -18,10 +18,10 @@ object GetWorkgroup {
|
||||
@Location("/workgroups/{workgroupId}")
|
||||
class WorkgroupRequest(val workgroupId: UUID)
|
||||
|
||||
fun Route.getWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) {
|
||||
fun Route.getWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
get<WorkgroupRequest> {
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
voter.assert { canView(workgroup, citizenOrNull) }
|
||||
ac.assert { canView(workgroup, citizenOrNull) }
|
||||
call.respond(workgroup)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
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.voter.assert
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -31,7 +31,7 @@ object GetWorkgroups {
|
||||
val members: List<UUID>? = members?.toUUID()
|
||||
}
|
||||
|
||||
fun Route.getWorkgroups(repo: WorkgroupRepository, voter: WorkgroupVoter) {
|
||||
fun Route.getWorkgroups(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
get<WorkgroupsRequest> {
|
||||
val workgroups =
|
||||
repo.find(
|
||||
@@ -42,7 +42,7 @@ object GetWorkgroups {
|
||||
it.search,
|
||||
WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members)
|
||||
)
|
||||
voter.assert { canView(workgroups.result, citizenOrNull) }
|
||||
ac.assert { canView(workgroups.result, citizenOrNull) }
|
||||
call.respond(workgroups)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package fr.dcproject.component.workgroup.routes.members
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
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.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -40,12 +40,12 @@ object AddMemberToWorkgroup {
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, voter: WorkgroupVoter) {
|
||||
fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
/* Add members to workgroup */
|
||||
post<WorkgroupsMembersRequest> {
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
call.getMembersFromRequest().let { members ->
|
||||
voter.assert { canAddMembers(workgroup, citizenOrNull) }
|
||||
ac.assert { canAddMembers(workgroup, citizenOrNull) }
|
||||
repo.addMembers(workgroup, members)
|
||||
}.let { members ->
|
||||
call.respond(HttpStatusCode.Created, members)
|
||||
|
||||
@@ -2,10 +2,10 @@ package fr.dcproject.component.workgroup.routes.members
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
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.call
|
||||
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<WorkgroupsMembersRequest> {
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
call.getMembersFromRequest().let { members ->
|
||||
voter.assert { canView(workgroup, citizenOrNull) }
|
||||
ac.assert { canView(workgroup, citizenOrNull) }
|
||||
repo.removeMembers(workgroup, members)
|
||||
}.let { members ->
|
||||
call.respond(HttpStatusCode.OK, members)
|
||||
|
||||
@@ -2,10 +2,10 @@ package fr.dcproject.component.workgroup.routes.members
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.WorkgroupVoter
|
||||
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.call
|
||||
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 */
|
||||
put<WorkgroupsMembersRequest> {
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
call.getMembersFromRequest().let { members ->
|
||||
voter.assert { canUpdateMembers(workgroup, citizenOrNull) }
|
||||
ac.assert { canUpdateMembers(workgroup, citizenOrNull) }
|
||||
repo.updateMembers(workgroup, members)
|
||||
}.let { members ->
|
||||
call.respond(HttpStatusCode.OK, members)
|
||||
|
||||
@@ -3,11 +3,11 @@ package fr.dcproject.routes
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
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.CommentVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.repository.CommentConstitutionRepository
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -28,10 +28,10 @@ object CommentConstitutionPaths {
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: CommentVoter) {
|
||||
fun Route.commentConstitution(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
get<CommentConstitutionPaths.ConstitutionCommentRequest> {
|
||||
val comments = repo.findByTarget(it.constitution)
|
||||
voter.assert { canView(comments.result, citizenOrNull) }
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(HttpStatusCode.OK, comments)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: Commen
|
||||
createdBy = citizen,
|
||||
content = content
|
||||
)
|
||||
voter.assert { canCreate(comment, citizenOrNull) }
|
||||
ac.assert { canCreate(comment, citizenOrNull) }
|
||||
repo.comment(comment)
|
||||
|
||||
call.respond(HttpStatusCode.Created, comment)
|
||||
@@ -50,7 +50,7 @@ fun Route.commentConstitution(repo: CommentConstitutionRepository, voter: Commen
|
||||
|
||||
get<CommentConstitutionPaths.CitizenCommentConstitutionRequest> {
|
||||
val comments = repo.findByCitizen(it.citizen)
|
||||
voter.assert { canView(comments.result, citizenOrNull) }
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(comments)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenWithUserI
|
||||
import fr.dcproject.entity.ConstitutionSimple
|
||||
import fr.dcproject.entity.ConstitutionSimple.TitleSimple
|
||||
import fr.dcproject.security.voter.ConstitutionVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.dcproject.security.assert
|
||||
import fr.dcproject.security.voter.ConstitutionAccessControl
|
||||
import fr.postgresjson.entity.UuidEntity
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.ktor.application.ApplicationCall
|
||||
@@ -88,21 +88,21 @@ object ConstitutionPaths {
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.constitution(repo: ConstitutionRepository, voter: ConstitutionVoter) {
|
||||
fun Route.constitution(repo: ConstitutionRepository, ac: ConstitutionAccessControl) {
|
||||
get<ConstitutionPaths.ConstitutionsRequest> {
|
||||
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)
|
||||
}
|
||||
|
||||
get<ConstitutionPaths.ConstitutionRequest> {
|
||||
voter.assert { canView(it.constitution, citizenOrNull) }
|
||||
ac.assert { canView(it.constitution, citizenOrNull) }
|
||||
call.respond(it.constitution)
|
||||
}
|
||||
|
||||
post<ConstitutionPaths.PostConstitutionRequest> {
|
||||
it.getNewConstitution(call).let { constitution ->
|
||||
voter.assert { canCreate(constitution, citizenOrNull) }
|
||||
ac.assert { canCreate(constitution, citizenOrNull) }
|
||||
repo.upsert(constitution)
|
||||
call.respond(constitution)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package fr.dcproject.voter
|
||||
package fr.dcproject.security
|
||||
|
||||
/** Responses of voters */
|
||||
enum class Vote {
|
||||
/** Responses of AccessControl */
|
||||
enum class AccessDecision {
|
||||
GRANTED,
|
||||
DENIED;
|
||||
|
||||
/** Helper to convert true/false to GRANTED/DENIED */
|
||||
companion object {
|
||||
fun toVote(lambda: () -> Boolean): Vote = when (lambda()) {
|
||||
fun toVote(lambda: () -> Boolean): AccessDecision = when (lambda()) {
|
||||
true -> GRANTED
|
||||
false -> DENIED
|
||||
}
|
||||
@@ -22,7 +22,7 @@ enum class Vote {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Voter {
|
||||
abstract class AccessControl {
|
||||
/**
|
||||
* A Shortcut for return a GrantedResponse
|
||||
*/
|
||||
@@ -37,20 +37,20 @@ abstract class Voter {
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
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) }
|
||||
.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()
|
||||
}
|
||||
|
||||
@@ -59,84 +59,84 @@ fun <T : Voter> T.assert(action: T.() -> VoterResponse) {
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
fun VoterResponses.assert() = this.getOneResponse().assert()
|
||||
fun AccessResponses.assert() = this.getOneResponse().assert()
|
||||
|
||||
class VoterDeniedException(private val voterResponses: VoterResponses) : Throwable(voterResponses.first().message) {
|
||||
constructor(voterResponse: VoterResponse) : this(listOf(voterResponse))
|
||||
class AccessDeniedException(private val accessResponses: AccessResponses) : Throwable(accessResponses.first().message) {
|
||||
constructor(accessResponse: AccessResponse) : this(listOf(accessResponse))
|
||||
|
||||
/**
|
||||
* Get first response
|
||||
*/
|
||||
fun first(): VoterResponse = voterResponses.first()
|
||||
fun first(): AccessResponse = accessResponses.first()
|
||||
|
||||
/**
|
||||
* Check if the error code is present into the responses
|
||||
*/
|
||||
fun hasErrorCode(code: String): Boolean = voterResponses
|
||||
.filter { it.vote == Vote.DENIED }
|
||||
fun hasErrorCode(code: String): Boolean = accessResponses
|
||||
.filter { it.decision == AccessDecision.DENIED }
|
||||
.any { it.code == code }
|
||||
|
||||
/**
|
||||
* Find and return the response than match with the error code
|
||||
*/
|
||||
fun getErrorCode(code: String): VoterResponse? = voterResponses
|
||||
.firstOrNull { it.vote == Vote.DENIED && it.code == code }
|
||||
fun getErrorCode(code: String): AccessResponse? = accessResponses
|
||||
.firstOrNull { it.decision == AccessDecision.DENIED && it.code == code }
|
||||
|
||||
/**
|
||||
* Get a list of messages of all responses
|
||||
*/
|
||||
fun getMessages(): List<String> = voterResponses
|
||||
fun getMessages(): List<String> = accessResponses
|
||||
.mapNotNull { it.message }
|
||||
|
||||
/**
|
||||
* Get the first message
|
||||
*/
|
||||
fun getFirstMessage(): String? = voterResponses
|
||||
fun getFirstMessage(): String? = accessResponses
|
||||
.first()
|
||||
.message
|
||||
}
|
||||
|
||||
/**
|
||||
* The response that all Voter method return
|
||||
* The response that all AccessControl method return
|
||||
* @see GrantedResponse
|
||||
* @see DeniedResponse
|
||||
*/
|
||||
sealed class VoterResponse(
|
||||
val vote: Vote,
|
||||
val voter: Voter,
|
||||
sealed class AccessResponse(
|
||||
val decision: AccessDecision,
|
||||
val accessControl: AccessControl,
|
||||
val message: String?,
|
||||
val code: String?
|
||||
) {
|
||||
/**
|
||||
* Convert response as boolean
|
||||
*/
|
||||
fun toBoolean(): Boolean = vote.toBoolean()
|
||||
fun toBoolean(): Boolean = decision.toBoolean()
|
||||
|
||||
/**
|
||||
* Throw Exception if response if DENIED
|
||||
*/
|
||||
fun assert() {
|
||||
if (this.vote == Vote.DENIED) {
|
||||
throw VoterDeniedException(this)
|
||||
if (this.decision == AccessDecision.DENIED) {
|
||||
throw AccessDeniedException(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GrantedResponse(
|
||||
voter: Voter,
|
||||
accessControl: AccessControl,
|
||||
message: String? = null,
|
||||
code: String? = null
|
||||
) : VoterResponse(Vote.GRANTED, voter, message, code)
|
||||
) : AccessResponse(AccessDecision.GRANTED, accessControl, message, code)
|
||||
|
||||
class DeniedResponse(
|
||||
voter: Voter,
|
||||
accessControl: AccessControl,
|
||||
message: String,
|
||||
code: String
|
||||
) : VoterResponse(Vote.DENIED, voter, message, code)
|
||||
) : AccessResponse(AccessDecision.DENIED, accessControl, message, code)
|
||||
|
||||
typealias VoterResponses = List<VoterResponse>
|
||||
typealias AccessResponses = List<AccessResponse>
|
||||
@@ -3,30 +3,30 @@ package fr.dcproject.security.voter
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.entity.ConstitutionS
|
||||
import fr.dcproject.entity.ConstitutionSimple
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.security.AccessControl
|
||||
import fr.dcproject.security.AccessResponse
|
||||
|
||||
class ConstitutionVoter : Voter() {
|
||||
fun canCreate(subject: ConstitutionS, citizen: CitizenI?): VoterResponse = when {
|
||||
class ConstitutionAccessControl : AccessControl() {
|
||||
fun canCreate(subject: ConstitutionS, citizen: CitizenI?): AccessResponse = when {
|
||||
citizen == null -> denied("You must be connected to create constitution", "constitution.create.notConnected")
|
||||
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) }
|
||||
|
||||
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")
|
||||
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")
|
||||
subject.createdBy.id != citizen.id -> denied("You cannot delete the constitution of other citizen", "constitution.delete.otherCitizen")
|
||||
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")
|
||||
subject.createdBy.id != citizen.id -> denied("You cannot update the constitution of other citizen", "constitution.update.otherCitizen")
|
||||
else -> granted()
|
||||
Reference in New Issue
Block a user