Big refactoring #77

Merged
flecomte merged 166 commits from refactoring-component-and-immutable into master 2021-03-24 19:06:07 +01:00
63 changed files with 462 additions and 462 deletions
Showing only changes of commit 49a03a57cb - Show all commits

View File

@@ -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)
}

View File

@@ -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> {

View File

@@ -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 {

View File

@@ -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) }
}
}

View File

@@ -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) }
}
}

View File

@@ -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,

View File

@@ -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))

View File

@@ -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")
}

View File

@@ -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))

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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()
}
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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>

View File

@@ -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()

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.ArticleVoter
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import fr.postgresjson.connexion.Paginated
import io.mockk.every
import io.mockk.mockk
@@ -23,8 +23,8 @@ import fr.dcproject.component.article.ArticleRepository as ArticleRepo
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class ArticleVoterTest {
@Tag("security")
internal class ArticleAccessControlTest {
private val tesla = CitizenCart(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User(
@@ -50,35 +50,35 @@ internal class ArticleVoterTest {
@Test
fun `creator can be view the article`() {
val article = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canView(article, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `other user can be view the article`() {
val article = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canView(article, einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@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 article2 = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canView(listOf(article, article2), einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `the no creator can not be view the article on draft`() {
val article = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canView(article, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
@@ -86,31 +86,31 @@ internal class ArticleVoterTest {
val article = getArticle(tesla)
val article2 = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canView(listOf(article, article2), einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can not view deleted article`() {
val article = getArticle(tesla).copy(deletedAt = DateTime.now())
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canView(article, tesla)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can delete article if owner`() {
val article = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canDelete(article, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not delete article if not owner`() {
val article = getArticle(tesla).copy(deletedAt = DateTime.now())
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canDelete(article, einstein)
.code `should be` "article.delete.notYours"
}
@@ -118,15 +118,15 @@ internal class ArticleVoterTest {
@Test
fun `can create article if logged`() {
val article = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canUpsert(article, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not create article if not logged`() {
val article = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canUpsert(article, null)
.code `should be` "article.create.notConnected"
}
@@ -134,15 +134,15 @@ internal class ArticleVoterTest {
@Test
fun `can update article if yours`() {
val article = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canUpsert(article, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not update article if not yours`() {
val article = getArticle(tesla)
ArticleVoter(getRepo(article))
ArticleAccessControl(getRepo(article))
.canUpsert(article, einstein)
.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.UserI
import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenVoter
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -17,8 +17,8 @@ import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class CitizenVoterTest {
@Tag("security")
internal class CitizenAccessControlTest {
private val tesla = CitizenBasic(
user = User(
username = "nicolas-tesla",
@@ -51,50 +51,50 @@ internal class CitizenVoterTest {
@Test
fun `can be view the citizen`() {
CitizenVoter()
CitizenAccessControl()
.canView(subject = einstein, connectedCitizen = tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view the citizen list`() {
CitizenVoter()
CitizenAccessControl()
.canView(subjects = listOf(tesla, einstein), connectedCitizen = einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not view deleted citizen`() {
CitizenVoter()
CitizenAccessControl()
.canView(subject = curie, connectedCitizen = tesla)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be update itself`() {
CitizenVoter()
CitizenAccessControl()
.canUpdate(subject = einstein, connectedCitizen = einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be update other citizen`() {
CitizenVoter()
CitizenAccessControl()
.canUpdate(subject = tesla, connectedCitizen = einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be change password of itself`() {
CitizenVoter()
CitizenAccessControl()
.canChangePassword(subject = einstein, connectedCitizen = einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be change password of other citizen`() {
CitizenVoter()
CitizenAccessControl()
.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.ArticleRef
@@ -7,11 +7,11 @@ import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenCart
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.CommentForView
import fr.dcproject.component.comment.generic.CommentVoter
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -23,8 +23,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class CommentVoterTest {
@Tag("security")
internal class CommentAccessControlTest {
private val tesla = Citizen(
user = User(
username = "nicolas-tesla",
@@ -99,57 +99,57 @@ internal class CommentVoterTest {
@Test
fun `can be view the comment`() {
CommentVoter()
CommentAccessControl()
.canView(comment1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view the comment list`() {
CommentVoter()
CommentAccessControl()
.canView(listOf(comment1, comment2), einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be update your comment`() {
CommentVoter()
CommentAccessControl()
.canUpdate(comment1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be update other comment`() {
CommentVoter()
CommentAccessControl()
.canUpdate(comment1, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be create a comment`() {
CommentVoter()
CommentAccessControl()
.canCreate(comment1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be create a comment if target is deleted`() {
CommentVoter()
CommentAccessControl()
.canCreate(commentTargetDeleted, tesla)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can not be create a comment with other creator`() {
CommentVoter()
CommentAccessControl()
.canCreate(comment1, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can not be create a comment if not connected`() {
CommentVoter()
CommentAccessControl()
.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.auth.User
@@ -8,9 +8,9 @@ import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.follow.Follow
import fr.dcproject.component.follow.FollowVoter
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -22,8 +22,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class FollowVoterTest {
@Tag("security")
internal class FollowAccessControlTest {
private val tesla = CitizenBasic(
user = User(
username = "nicolas-tesla",
@@ -97,57 +97,57 @@ internal class FollowVoterTest {
@Test
fun `can be view the follow`() {
FollowVoter()
FollowAccessControl()
.canView(follow1, tesla2)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view the follow list`() {
FollowVoter()
FollowAccessControl()
.canView(listOf(follow1), tesla2)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view your anonymous follow`() {
FollowVoter()
FollowAccessControl()
.canView(followAnon, einstein3)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be view the anonymous follow of other`() {
FollowVoter()
FollowAccessControl()
.canView(followAnon, tesla2)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be follow article`() {
FollowVoter()
FollowAccessControl()
.canCreate(follow1, tesla2)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be follow article if not connected`() {
FollowVoter()
FollowAccessControl()
.canCreate(follow1, null)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be unfollow article`() {
FollowVoter()
FollowAccessControl()
.canDelete(follow1, tesla2)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be unfollow article if not connected`() {
FollowVoter()
FollowAccessControl()
.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.auth.User
@@ -6,11 +6,11 @@ import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
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.OpinionChoice
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -22,8 +22,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class OpinionVoterTest {
@Tag("security")
internal class OpinionAccessControlTest {
private val tesla = CitizenBasic(
user = User(
username = "nicolas-tesla",
@@ -74,50 +74,50 @@ internal class OpinionVoterTest {
@Test
fun `can be view the opinion`() {
OpinionVoter()
OpinionAccessControl()
.canView(opinion1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view the opinion list`() {
OpinionVoter()
OpinionAccessControl()
.canView(listOf(opinion1), tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be opinion an article`() {
OpinionVoter()
OpinionAccessControl()
.canCreate(opinion1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be opinion if not connected`() {
OpinionVoter()
OpinionAccessControl()
.canCreate(opinion1, null)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be remove opinion`() {
OpinionVoter()
OpinionAccessControl()
.canDelete(opinion1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be remove opinion if not connected`() {
OpinionVoter()
OpinionAccessControl()
.canDelete(opinion1, null)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can not be remove opinion of other user`() {
OpinionVoter()
OpinionAccessControl()
.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.auth.User
@@ -6,9 +6,9 @@ import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
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.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -20,8 +20,8 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class OpinionChoiceVoterTest {
@Tag("security")
internal class OpinionChoiceAccessControlTest {
private val tesla = CitizenBasic(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User(
@@ -57,15 +57,15 @@ internal class OpinionChoiceVoterTest {
@Test
fun `can be view the opinion choice`() {
OpinionChoiceVoter()
OpinionChoiceAccessControl()
.canView(choice1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view the opinion choice list`() {
OpinionChoiceVoter()
OpinionChoiceAccessControl()
.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.auth.User
@@ -7,10 +7,10 @@ import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
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.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -23,8 +23,8 @@ import fr.dcproject.component.vote.entity.Vote as VoteEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class VoteVoterTest {
@Tag("security")
internal class VoteAccessControlTest {
private val tesla = Citizen(
id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"),
user = User(
@@ -101,43 +101,43 @@ internal class VoteVoterTest {
@Test
fun `can be view your the vote`() {
VoteVoter()
VoteAccessControl()
.canView(vote1, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be view vote of other`() {
VoteVoter()
VoteAccessControl()
.canView(vote1, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be view your votes list`() {
VoteVoter()
VoteAccessControl()
.canView(listOf(vote1), tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be vote an article`() {
VoteVoter()
VoteAccessControl()
.canCreate(voteForUpdate, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be vote if not connected`() {
VoteVoter()
VoteAccessControl()
.canCreate(voteForUpdate, null)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can not be vote an article if article is deleted`() {
VoteVoter()
VoteAccessControl()
.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.UserI
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
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.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.dcproject.security.AccessDecision.DENIED
import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
@@ -21,8 +21,8 @@ import fr.dcproject.component.workgroup.Workgroup as WorkgroupEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("voter")
internal class WorkgroupVoterTest {
@Tag("security")
internal class WorkgroupAccessControlTest {
private val tesla = CitizenBasic(
user = User(
username = "nicolas-tesla",
@@ -73,78 +73,78 @@ internal class WorkgroupVoterTest {
@Test
fun `can be view your workgroup`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canView(workgroupPublic, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view your workgroup if is not public`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canView(workgroupAnon, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be view workgroup of other if is public`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canView(workgroupPublic, einstein)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be view workgroup of other if is not public`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canView(workgroupAnon, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be view your workgroup list`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canView(listOf(workgroupPublic, workgroupAnon), tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can be create workgroup`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canCreate(workgroupPublic, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be create workgroup if not connected`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canCreate(workgroupPublic, null)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be delete workgroup if owner`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canDelete(workgroupPublic, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be delete workgroup if not owner`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canDelete(workgroupPublic, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
@Test
fun `can be update workgroup if owner`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canUpdate(workgroupPublic, tesla)
.vote `should be` GRANTED
.decision `should be` GRANTED
}
@Test
fun `can not be update workgroup if not owner`() {
WorkgroupVoter()
WorkgroupAccessControl()
.canUpdate(workgroupPublic, einstein)
.vote `should be` DENIED
.decision `should be` DENIED
}
}