Error codes #84

Merged
flecomte merged 5 commits from 69 into master 2021-03-31 02:53:58 +02:00
41 changed files with 353 additions and 36 deletions

View File

@@ -6,17 +6,14 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.joda.JodaModule import com.fasterxml.jackson.datatype.joda.JodaModule
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.dcproject.application.Env.PROD import fr.dcproject.application.Env.PROD
import fr.dcproject.application.Env.TEST import fr.dcproject.application.Env.TEST
import fr.dcproject.common.security.AccessDeniedException import fr.dcproject.application.http.statusPagesInstallation
import fr.dcproject.component.article.articleKoinModule import fr.dcproject.component.article.articleKoinModule
import fr.dcproject.component.article.routes.installArticleRoutes import fr.dcproject.component.article.routes.installArticleRoutes
import fr.dcproject.component.auth.ForbiddenException
import fr.dcproject.component.auth.authKoinModule import fr.dcproject.component.auth.authKoinModule
import fr.dcproject.component.auth.jwt.jwtInstallation import fr.dcproject.component.auth.jwt.jwtInstallation
import fr.dcproject.component.auth.routes.installAuthRoutes import fr.dcproject.component.auth.routes.installAuthRoutes
import fr.dcproject.component.auth.user
import fr.dcproject.component.citizen.citizenKoinModule import fr.dcproject.component.citizen.citizenKoinModule
import fr.dcproject.component.citizen.routes.installCitizenRoutes import fr.dcproject.component.citizen.routes.installCitizenRoutes
import fr.dcproject.component.comment.article.routes.installCommentArticleRoutes import fr.dcproject.component.comment.article.routes.installCommentArticleRoutes
@@ -41,7 +38,6 @@ import fr.dcproject.component.workgroup.workgroupKoinModule
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.ApplicationStopped import io.ktor.application.ApplicationStopped
import io.ktor.application.call
import io.ktor.application.install import io.ktor.application.install
import io.ktor.auth.Authentication import io.ktor.auth.Authentication
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@@ -51,17 +47,14 @@ import io.ktor.features.CORS
import io.ktor.features.CallLogging import io.ktor.features.CallLogging
import io.ktor.features.ContentNegotiation import io.ktor.features.ContentNegotiation
import io.ktor.features.DataConversion import io.ktor.features.DataConversion
import io.ktor.features.NotFoundException
import io.ktor.features.StatusPages import io.ktor.features.StatusPages
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.http.cio.websocket.pingPeriod import io.ktor.http.cio.websocket.pingPeriod
import io.ktor.http.cio.websocket.timeout import io.ktor.http.cio.websocket.timeout
import io.ktor.jackson.jackson import io.ktor.jackson.jackson
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Locations import io.ktor.locations.Locations
import io.ktor.response.respond
import io.ktor.routing.Routing import io.ktor.routing.Routing
import io.ktor.server.jetty.EngineMain import io.ktor.server.jetty.EngineMain
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
@@ -73,7 +66,6 @@ import org.koin.ktor.ext.Koin
import org.koin.ktor.ext.get import org.koin.ktor.ext.get
import org.slf4j.event.Level import org.slf4j.event.Level
import java.time.Duration import java.time.Duration
import java.util.concurrent.CompletionException
fun main(args: Array<String>): Unit = EngineMain.main(args) fun main(args: Array<String>): Unit = EngineMain.main(args)
@@ -171,26 +163,7 @@ fun Application.module(env: Env = PROD) {
installDocRoutes() installDocRoutes()
} }
install(StatusPages) { install(StatusPages, statusPagesInstallation())
exception<CompletionException> { e ->
val parent = e.cause?.cause
if (parent is GenericDatabaseException) {
call.respond(HttpStatusCode.BadRequest, parent.errorMessage.message!!)
} else {
throw e
}
}
exception<NotFoundException> { e ->
call.respond(HttpStatusCode.NotFound, e.message!!)
}
exception<AccessDeniedException> {
if (call.user == null) call.respond(HttpStatusCode.Unauthorized)
else call.respond(HttpStatusCode.Forbidden)
}
exception<ForbiddenException> {
call.respond(HttpStatusCode.Forbidden)
}
}
install(CORS) { install(CORS) {
method(HttpMethod.Options) method(HttpMethod.Options)

View File

@@ -0,0 +1,82 @@
package fr.dcproject.application.http
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.dcproject.common.security.AccessDeniedException
import fr.dcproject.component.auth.ForbiddenException
import fr.dcproject.component.auth.user
import io.ktor.application.call
import io.ktor.features.NotFoundException
import io.ktor.features.StatusPages
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import java.util.concurrent.CompletionException
class HttpError(
statusCode: HttpStatusCode,
val cause: Throwable? = null,
val type: String? = null,
val title: String = cause?.message ?: statusCode.description,
val detail: String? = null,
val invalidParams: List<InvalidParam>? = null,
val stackTrace: String? = cause?.stackTraceToString()
) {
val statusCode: Int = statusCode.value
data class InvalidParam(
val name: String,
val reason: String
)
}
fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
exception<CompletionException> { e ->
val parent = e.cause?.cause
if (parent is GenericDatabaseException) {
HttpError(
HttpStatusCode.BadRequest,
cause = parent
).let {
call.respond(HttpStatusCode.BadRequest, it)
}
} else {
HttpError(
HttpStatusCode.BadRequest,
cause = e
).let {
call.respond(HttpStatusCode.InternalServerError, it)
}
}
}
exception<NotFoundException> { e ->
HttpError(
HttpStatusCode.NotFound,
cause = e
).let {
call.respond(HttpStatusCode.NotFound, it)
}
}
exception<AccessDeniedException> { e ->
if (call.user == null) {
HttpError(
HttpStatusCode.Unauthorized,
cause = e
).let {
call.respond(HttpStatusCode.Unauthorized, it)
}
} else {
HttpError(
HttpStatusCode.Forbidden,
cause = e
).let {
call.respond(HttpStatusCode.Forbidden, it)
}
}
}
exception<ForbiddenException> { e ->
HttpError(
HttpStatusCode.Forbidden,
cause = e
).let {
call.respond(HttpStatusCode.Forbidden, it)
}
}
}

View File

@@ -8,6 +8,7 @@ import fr.dcproject.component.article.database.ArticleRepository
import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.notification.ArticleUpdateNotification import fr.dcproject.component.notification.ArticleUpdateNotification
import fr.dcproject.component.notification.Publisher import fr.dcproject.component.notification.Publisher
import fr.dcproject.component.workgroup.database.WorkgroupRef import fr.dcproject.component.workgroup.database.WorkgroupRef
@@ -54,6 +55,7 @@ object UpsertArticle {
} }
post<UpsertArticleRequest> { post<UpsertArticleRequest> {
mustBeAuth()
val article = call.convertRequestToEntity() val article = call.convertRequestToEntity()
ac.assert { canUpsert(article, citizenOrNull) } ac.assert { canUpsert(article, citizenOrNull) }
repo.upsert(article)?.let { a -> repo.upsert(article)?.let { a ->

View File

@@ -26,7 +26,21 @@ val ApplicationCall.citizenOrNull: CitizenEntity?
GlobalContext.get().koin.get<CitizenRepository>().findByUser(it) GlobalContext.get().koin.get<CitizenRepository>().findByUser(it)
} }
val ApplicationCall.isAuth: Boolean
get() = citizenOrNull == null
fun ApplicationCall.mustBeAuth() {
citizenOrNull ?: throw ForbiddenException("No User Connected")
}
val PipelineContext<Unit, ApplicationCall>.citizen get() = context.citizen val PipelineContext<Unit, ApplicationCall>.citizen get() = context.citizen
val PipelineContext<Unit, ApplicationCall>.citizenOrNull get() = context.citizenOrNull val PipelineContext<Unit, ApplicationCall>.citizenOrNull get() = context.citizenOrNull
val ApplicationCall.user get() = authentication.principal<User>() val ApplicationCall.user get() = authentication.principal<User>()
val PipelineContext<Unit, ApplicationCall>.isAuth: Boolean
get() = citizenOrNull == null
fun PipelineContext<Unit, ApplicationCall>.mustBeAuth() {
citizenOrNull ?: throw ForbiddenException("No User Connected")
}

View File

@@ -6,6 +6,7 @@ import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.database.UserRepository import fr.dcproject.component.auth.database.UserRepository
import fr.dcproject.component.auth.database.UserWithPassword import fr.dcproject.component.auth.database.UserWithPassword
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.CitizenAccessControl import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import io.ktor.application.call import io.ktor.application.call
@@ -29,6 +30,7 @@ object ChangeMyPassword {
fun Route.changeMyPassword(ac: CitizenAccessControl, userRepository: UserRepository) { fun Route.changeMyPassword(ac: CitizenAccessControl, userRepository: UserRepository) {
put<ChangePasswordCitizenRequest> { put<ChangePasswordCitizenRequest> {
mustBeAuth()
ac.assert { canChangePassword(it.citizen, citizenOrNull) } ac.assert { canChangePassword(it.citizen, citizenOrNull) }
val content = call.receiveOrBadRequest<ChangePasswordCitizenRequest.Input>() val content = call.receiveOrBadRequest<ChangePasswordCitizenRequest.Input>()
userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword)) ?: throw BadRequestException("Bad Password") userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword)) ?: throw BadRequestException("Bad Password")

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.citizen.routes
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.CitizenAccessControl import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.database.CitizenCreator import fr.dcproject.component.citizen.database.CitizenCreator
import fr.dcproject.component.citizen.database.CitizenRepository import fr.dcproject.component.citizen.database.CitizenRepository
@@ -30,6 +31,7 @@ object FindCitizens {
fun Route.findCitizen(ac: CitizenAccessControl, repo: CitizenRepository) { fun Route.findCitizen(ac: CitizenAccessControl, repo: CitizenRepository) {
get<CitizensRequest> { get<CitizensRequest> {
mustBeAuth()
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search) val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
ac.assert { canView(citizens.result, citizenOrNull) } ac.assert { canView(citizens.result, citizenOrNull) }
call.respond( call.respond(

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.citizen.routes
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.CitizenAccessControl import fr.dcproject.component.citizen.CitizenAccessControl
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -22,6 +23,7 @@ object GetCurrentCitizen {
fun Route.getCurrentCitizen(ac: CitizenAccessControl) { fun Route.getCurrentCitizen(ac: CitizenAccessControl) {
get<CurrentCitizenRequest> { get<CurrentCitizenRequest> {
mustBeAuth()
val currentUser = citizenOrNull val currentUser = citizenOrNull
if (currentUser === null) { if (currentUser === null) {
call.respond(HttpStatusCode.Unauthorized) call.respond(HttpStatusCode.Unauthorized)

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.citizen.routes
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.CitizenAccessControl import fr.dcproject.component.citizen.CitizenAccessControl
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.citizen.database.CitizenRepository import fr.dcproject.component.citizen.database.CitizenRepository
@@ -26,6 +27,7 @@ object GetOneCitizen {
fun Route.getOneCitizen(ac: CitizenAccessControl, citizenRepository: CitizenRepository) { fun Route.getOneCitizen(ac: CitizenAccessControl, citizenRepository: CitizenRepository) {
get<CitizenRequest> { get<CitizenRequest> {
mustBeAuth()
val citizen = citizenRepository.findById(it.citizen.id) ?: throw NotFoundException("Citizen not found ${it.citizen.id}") val citizen = citizenRepository.findById(it.citizen.id) ?: throw NotFoundException("Citizen not found ${it.citizen.id}")
ac.assert { canView(citizen, citizenOrNull) } ac.assert { canView(citizen, citizenOrNull) }

View File

@@ -6,6 +6,7 @@ import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.comment.article.database.CommentArticleRepository import fr.dcproject.component.comment.article.database.CommentArticleRepository
import fr.dcproject.component.comment.article.routes.CreateCommentArticle.PostArticleCommentRequest.Input import fr.dcproject.component.comment.article.routes.CreateCommentArticle.PostArticleCommentRequest.Input
import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.generic.CommentAccessControl
@@ -30,6 +31,7 @@ object CreateCommentArticle {
fun Route.createCommentArticle(repo: CommentArticleRepository, ac: CommentAccessControl) { fun Route.createCommentArticle(repo: CommentArticleRepository, ac: CommentAccessControl) {
post<PostArticleCommentRequest> { post<PostArticleCommentRequest> {
mustBeAuth()
call.receiveOrBadRequest<Input>().run { call.receiveOrBadRequest<Input>().run {
CommentForUpdate( CommentForUpdate(
target = it.article, target = it.article,

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.comment.article.routes
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.comment.article.database.CommentArticleRepository import fr.dcproject.component.comment.article.database.CommentArticleRepository
import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.generic.CommentAccessControl
@@ -25,6 +26,7 @@ object GetCitizenArticleComments {
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) { fun Route.getCitizenArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
get<CitizenCommentArticleRequest> { get<CitizenCommentArticleRequest> {
mustBeAuth()
repo.findByCitizen(it.citizen).let { comments -> repo.findByCitizen(it.citizen).let { comments ->
ac.assert { canView(comments.result, citizenOrNull) } ac.assert { canView(comments.result, citizenOrNull) }
call.respond( call.respond(

View File

@@ -5,6 +5,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.comment.constitution.database.CommentConstitutionRepository import fr.dcproject.component.comment.constitution.database.CommentConstitutionRepository
import fr.dcproject.component.comment.constitution.routes.CreateConstitutionComment.CreateConstitutionCommentRequest.Input import fr.dcproject.component.comment.constitution.routes.CreateConstitutionComment.CreateConstitutionCommentRequest.Input
import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.generic.CommentAccessControl
@@ -30,6 +31,7 @@ object CreateConstitutionComment {
fun Route.createConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) { fun Route.createConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
post<CreateConstitutionCommentRequest> { post<CreateConstitutionCommentRequest> {
mustBeAuth()
call.receiveOrBadRequest<Input>().run { call.receiveOrBadRequest<Input>().run {
CommentForUpdate( CommentForUpdate(
target = it.constitution, target = it.constitution,

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.comment.constitution.routes
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.comment.constitution.database.CommentConstitutionRepository import fr.dcproject.component.comment.constitution.database.CommentConstitutionRepository
import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.generic.CommentAccessControl
@@ -25,6 +26,7 @@ object GetCitizenCommentConstitution {
fun Route.getCitizenCommentConstitution(repo: CommentConstitutionRepository, ac: CommentAccessControl) { fun Route.getCitizenCommentConstitution(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
get<GetCitizenCommentConstitutionRequest> { get<GetCitizenCommentConstitutionRequest> {
mustBeAuth()
val comments = repo.findByCitizen(it.citizen) val comments = repo.findByCitizen(it.citizen)
ac.assert { canView(comments.result, citizenOrNull) } ac.assert { canView(comments.result, citizenOrNull) }
call.respond( call.respond(

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.database.CommentForUpdate import fr.dcproject.component.comment.generic.database.CommentForUpdate
import fr.dcproject.component.comment.generic.database.CommentRef import fr.dcproject.component.comment.generic.database.CommentRef
@@ -29,6 +30,7 @@ object CreateCommentChildren {
fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) { fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
post<CreateCommentChildrenRequest> { post<CreateCommentChildrenRequest> {
mustBeAuth()
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found") val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
val newComment = CommentForUpdate( val newComment = CommentForUpdate(
content = call.receiveOrBadRequest<CreateCommentChildrenRequest.Input>().content, content = call.receiveOrBadRequest<CreateCommentChildrenRequest.Input>().content,

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.generic.CommentAccessControl
import fr.dcproject.component.comment.generic.database.CommentRef import fr.dcproject.component.comment.generic.database.CommentRef
import fr.dcproject.component.comment.generic.database.CommentRepository import fr.dcproject.component.comment.generic.database.CommentRepository
@@ -28,6 +29,7 @@ object EditComment {
fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) { fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) {
put<EditCommentRequest> { put<EditCommentRequest> {
mustBeAuth()
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found") val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
ac.assert { canUpdate(comment, citizenOrNull) } ac.assert { canUpdate(comment, citizenOrNull) }

View File

@@ -6,6 +6,7 @@ import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.Citizen import fr.dcproject.component.citizen.database.Citizen
import fr.dcproject.component.citizen.database.CitizenWithUserI import fr.dcproject.component.citizen.database.CitizenWithUserI
import fr.dcproject.component.constitution.ConstitutionAccessControl import fr.dcproject.component.constitution.ConstitutionAccessControl
@@ -68,6 +69,7 @@ object CreateConstitution {
fun Route.createConstitution(repo: ConstitutionRepository, ac: ConstitutionAccessControl) { fun Route.createConstitution(repo: ConstitutionRepository, ac: ConstitutionAccessControl) {
post<PostConstitutionRequest> { post<PostConstitutionRequest> {
mustBeAuth()
getNewConstitution(call.receiveOrBadRequest(), citizen).let { getNewConstitution(call.receiveOrBadRequest(), citizen).let {
ac.assert { canCreate(it, citizenOrNull) } ac.assert { canCreate(it, citizenOrNull) }
val c = repo.upsert(it) ?: error("Unable to create Constitution") val c = repo.upsert(it) ?: error("Unable to create Constitution")

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.follow.FollowAccessControl import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.database.FollowArticleRepository import fr.dcproject.component.follow.database.FollowArticleRepository
import fr.dcproject.component.follow.database.FollowForUpdate import fr.dcproject.component.follow.database.FollowForUpdate
@@ -25,6 +26,7 @@ object FollowArticle {
fun Route.followArticle(repo: FollowArticleRepository, ac: FollowAccessControl) { fun Route.followArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
post<ArticleFollowRequest> { post<ArticleFollowRequest> {
mustBeAuth()
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen) val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
ac.assert { canCreate(follow, citizenOrNull) } ac.assert { canCreate(follow, citizenOrNull) }
repo.follow(follow) repo.follow(follow)

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.article
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.follow.FollowAccessControl import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.database.FollowArticleRepository import fr.dcproject.component.follow.database.FollowArticleRepository
@@ -25,6 +26,7 @@ object GetMyFollowsArticle {
fun Route.getMyFollowsArticle(repo: FollowArticleRepository, ac: FollowAccessControl) { fun Route.getMyFollowsArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
get<CitizenFollowArticleRequest> { get<CitizenFollowArticleRequest> {
mustBeAuth()
val follows = repo.findByCitizen(it.citizen) val follows = repo.findByCitizen(it.citizen)
ac.assert { canView(follows.result, citizenOrNull) } ac.assert { canView(follows.result, citizenOrNull) }
call.respond( call.respond(

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.follow.FollowAccessControl import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.database.FollowArticleRepository import fr.dcproject.component.follow.database.FollowArticleRepository
import fr.dcproject.component.follow.database.FollowForUpdate import fr.dcproject.component.follow.database.FollowForUpdate
@@ -25,6 +26,7 @@ object UnfollowArticle {
fun Route.unfollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) { fun Route.unfollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
delete<ArticleFollowRequest> { delete<ArticleFollowRequest> {
mustBeAuth()
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen) val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
ac.assert { canDelete(follow, citizenOrNull) } ac.assert { canDelete(follow, citizenOrNull) }
repo.unfollow(follow) repo.unfollow(follow)

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.constitution.database.ConstitutionRef import fr.dcproject.component.constitution.database.ConstitutionRef
import fr.dcproject.component.follow.FollowAccessControl import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.database.FollowConstitutionRepository import fr.dcproject.component.follow.database.FollowConstitutionRepository
@@ -25,6 +26,7 @@ object FollowConstitution {
fun Route.followConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) { fun Route.followConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
post<ConstitutionFollowRequest> { post<ConstitutionFollowRequest> {
mustBeAuth()
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen) val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
ac.assert { canCreate(follow, citizenOrNull) } ac.assert { canCreate(follow, citizenOrNull) }
repo.follow(follow) repo.follow(follow)

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.follow.FollowAccessControl import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.database.FollowConstitutionRepository import fr.dcproject.component.follow.database.FollowConstitutionRepository
@@ -25,6 +26,7 @@ object GetMyFollowsConstitution {
fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) { fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
get<CitizenFollowConstitutionRequest> { get<CitizenFollowConstitutionRequest> {
mustBeAuth()
val follows = repo.findByCitizen(it.citizen) val follows = repo.findByCitizen(it.citizen)
ac.assert { canView(follows.result, citizenOrNull) } ac.assert { canView(follows.result, citizenOrNull) }
call.respond( call.respond(

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.constitution
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.constitution.database.ConstitutionRef import fr.dcproject.component.constitution.database.ConstitutionRef
import fr.dcproject.component.follow.FollowAccessControl import fr.dcproject.component.follow.FollowAccessControl
import fr.dcproject.component.follow.database.FollowConstitutionRepository import fr.dcproject.component.follow.database.FollowConstitutionRepository
@@ -25,6 +26,7 @@ object UnfollowConstitution {
fun Route.unfollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) { fun Route.unfollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
delete<ConstitutionUnfollowRequest> { delete<ConstitutionUnfollowRequest> {
mustBeAuth()
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen) val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
ac.assert { canDelete(follow, citizenOrNull) } ac.assert { canDelete(follow, citizenOrNull) }
repo.unfollow(follow) repo.unfollow(follow)

View File

@@ -5,6 +5,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.toUUID import fr.dcproject.common.utils.toUUID
import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.opinion.OpinionAccessControl import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.database.Opinion import fr.dcproject.component.opinion.database.Opinion
@@ -31,6 +32,7 @@ object GetCitizenOpinions {
fun Route.getCitizenOpinions(repo: OpinionArticleRepository, ac: OpinionAccessControl) { fun Route.getCitizenOpinions(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
get<CitizenOpinions> { get<CitizenOpinions> {
mustBeAuth()
val opinionsEntities: List<Opinion<ArticleRef>> = repo.findCitizenOpinionsByTargets(it.citizen, it.id) val opinionsEntities: List<Opinion<ArticleRef>> = repo.findCitizenOpinionsByTargets(it.citizen, it.id)
ac.assert { canView(opinionsEntities, citizenOrNull) } ac.assert { canView(opinionsEntities, citizenOrNull) }

View File

@@ -5,6 +5,7 @@ import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.opinion.OpinionAccessControl import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.database.Opinion import fr.dcproject.component.opinion.database.Opinion
@@ -37,6 +38,7 @@ object GetMyOpinionsArticle {
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) { fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
get<CitizenOpinionsArticleRequest> { get<CitizenOpinionsArticleRequest> {
mustBeAuth()
val opinions: Paginated<Opinion<TargetRef>> = repo.findCitizenOpinions(citizen, it.page, it.limit) val opinions: Paginated<Opinion<TargetRef>> = repo.findCitizenOpinions(citizen, it.page, it.limit)
ac.assert { canView(opinions.result, citizenOrNull) } ac.assert { canView(opinions.result, citizenOrNull) }
call.respond( call.respond(

View File

@@ -6,6 +6,7 @@ import fr.dcproject.common.utils.toUUID
import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.opinion.OpinionAccessControl import fr.dcproject.component.opinion.OpinionAccessControl
import fr.dcproject.component.opinion.database.OpinionChoiceRef import fr.dcproject.component.opinion.database.OpinionChoiceRef
import fr.dcproject.component.opinion.database.OpinionForUpdate import fr.dcproject.component.opinion.database.OpinionForUpdate
@@ -34,6 +35,7 @@ object OpinionArticle {
fun Route.setOpinionOnArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) { fun Route.setOpinionOnArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
put<ArticleOpinion> { put<ArticleOpinion> {
mustBeAuth()
call.receiveOrBadRequest<ArticleOpinion.Body>().ids.map { id -> call.receiveOrBadRequest<ArticleOpinion.Body>().ids.map { id ->
OpinionForUpdate( OpinionForUpdate(
choice = OpinionChoiceRef(id), choice = OpinionChoiceRef(id),

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.toUUID import fr.dcproject.common.utils.toUUID
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.vote.VoteAccessControl import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.database.VoteRepository import fr.dcproject.component.vote.database.VoteRepository
@@ -26,6 +27,7 @@ object GetCitizenVotes {
fun Route.getCitizenVote(repo: VoteRepository, ac: VoteAccessControl) { fun Route.getCitizenVote(repo: VoteRepository, ac: VoteAccessControl) {
get<CitizenVotesRequest> { get<CitizenVotesRequest> {
mustBeAuth()
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id) val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
if (votes.isNotEmpty()) { if (votes.isNotEmpty()) {
ac.assert { canView(votes, citizenOrNull) } ac.assert { canView(votes, citizenOrNull) }

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.vote.routes
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.vote.VoteAccessControl import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.database.VoteArticleRepository import fr.dcproject.component.vote.database.VoteArticleRepository
@@ -31,6 +32,7 @@ object GetCitizenVotesOnArticle {
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) { fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
get<CitizenVoteArticleRequest> { get<CitizenVoteArticleRequest> {
mustBeAuth()
val votes = repo.findByCitizen(it.citizen, it.page, it.limit) val votes = repo.findByCitizen(it.citizen, it.page, it.limit)
ac.assert { canView(votes.result, citizenOrNull) } ac.assert { canView(votes.result, citizenOrNull) }

View File

@@ -6,6 +6,7 @@ import fr.dcproject.component.article.database.ArticleRef
import fr.dcproject.component.article.database.ArticleRepository import fr.dcproject.component.article.database.ArticleRepository
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.vote.VoteAccessControl import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.database.VoteArticleRepository import fr.dcproject.component.vote.database.VoteArticleRepository
import fr.dcproject.component.vote.database.VoteForUpdate import fr.dcproject.component.vote.database.VoteForUpdate
@@ -29,6 +30,7 @@ object PutVoteOnArticle {
fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) { fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) {
put<ArticleVoteRequest> { put<ArticleVoteRequest> {
mustBeAuth()
val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>() val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>()
val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found") val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
val vote = VoteForUpdate( val vote = VoteForUpdate(

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.comment.generic.database.CommentRepository import fr.dcproject.component.comment.generic.database.CommentRepository
import fr.dcproject.component.vote.VoteAccessControl import fr.dcproject.component.vote.VoteAccessControl
import fr.dcproject.component.vote.database.VoteCommentRepository import fr.dcproject.component.vote.database.VoteCommentRepository
@@ -26,6 +27,7 @@ object PutVoteOnComment {
fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, ac: VoteAccessControl) { fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, ac: VoteAccessControl) {
put<CommentVoteRequest> { put<CommentVoteRequest> {
mustBeAuth()
val comment = commentRepo.findById(it.comment)!! val comment = commentRepo.findById(it.comment)!!
val content = call.receiveOrBadRequest<CommentVoteRequest.Content>() val content = call.receiveOrBadRequest<CommentVoteRequest.Content>()
val vote = VoteForUpdate( val vote = VoteForUpdate(

View File

@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.constitution.database.ConstitutionRef import fr.dcproject.component.constitution.database.ConstitutionRef
import fr.dcproject.component.constitution.database.ConstitutionRepository import fr.dcproject.component.constitution.database.ConstitutionRepository
import fr.dcproject.component.vote.VoteAccessControl import fr.dcproject.component.vote.VoteAccessControl
@@ -30,6 +31,7 @@ object PutVoteOnConstitution {
fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl, constitutionRepo: ConstitutionRepository) { fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl, constitutionRepo: ConstitutionRepository) {
put<ConstitutionVoteRequest> { put<ConstitutionVoteRequest> {
mustBeAuth()
val constitution = constitutionRepo.findById(it.constitution.id) ?: throw NotFoundException("Unable to find constitution ${it.constitution.id}") val constitution = constitutionRepo.findById(it.constitution.id) ?: throw NotFoundException("Unable to find constitution ${it.constitution.id}")
val content = call.receiveOrBadRequest<Input>() val content = call.receiveOrBadRequest<Input>()
val vote = VoteForUpdate( val vote = VoteForUpdate(

View File

@@ -5,6 +5,7 @@ import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizen import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
@@ -33,6 +34,7 @@ object CreateWorkgroup {
fun Route.createWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { fun Route.createWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
post<PostWorkgroupRequest> { post<PostWorkgroupRequest> {
mustBeAuth()
call.receiveOrBadRequest<Input>().run { call.receiveOrBadRequest<Input>().run {
WorkgroupForUpdate( WorkgroupForUpdate(
id ?: UUID.randomUUID(), id ?: UUID.randomUUID(),

View File

@@ -2,6 +2,7 @@ package fr.dcproject.component.workgroup.routes
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
import io.ktor.application.call import io.ktor.application.call
@@ -20,6 +21,7 @@ object DeleteWorkgroup {
fun Route.deleteWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { fun Route.deleteWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
delete<DeleteWorkgroupRequest> { delete<DeleteWorkgroupRequest> {
mustBeAuth()
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
ac.assert { canDelete(workgroup, citizenOrNull) } ac.assert { canDelete(workgroup, citizenOrNull) }
repo.delete(workgroup) repo.delete(workgroup)

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
@@ -31,6 +32,7 @@ object EditWorkgroup {
fun Route.editWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { fun Route.editWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
put<PutWorkgroupRequest> { put<PutWorkgroupRequest> {
mustBeAuth()
repo.findById(it.workgroupId)?.let { old -> repo.findById(it.workgroupId)?.let { old ->
call.receiveOrBadRequest<Input>().run { call.receiveOrBadRequest<Input>().run {
WorkgroupForUpdate( WorkgroupForUpdate(

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes.members
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
@@ -44,6 +45,7 @@ object AddMemberToWorkgroup {
fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
/* Add members to workgroup */ /* Add members to workgroup */
post<WorkgroupsMembersRequest> { post<WorkgroupsMembersRequest> {
mustBeAuth()
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
ac.assert { canAddMembers(workgroup, citizenOrNull) } ac.assert { canAddMembers(workgroup, citizenOrNull) }

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes.members
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
@@ -35,6 +36,7 @@ object DeleteMembersOfWorkgroup {
fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
/* Delete members of workgroup */ /* Delete members of workgroup */
delete<WorkgroupsMembersRequest> { delete<WorkgroupsMembersRequest> {
mustBeAuth()
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest() call.getMembersFromRequest()
.let { members -> .let { members ->

View File

@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes.members
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.auth.mustBeAuth
import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRef
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
@@ -42,6 +43,7 @@ object UpdateMemberOfWorkgroup {
fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
/* Update members of workgroup */ /* Update members of workgroup */
put<WorkgroupsMembersRequest> { put<WorkgroupsMembersRequest> {
mustBeAuth()
repo.findById(it.workgroupId)?.let { workgroup -> repo.findById(it.workgroupId)?.let { workgroup ->
call.getMembersFromRequest().let { members -> call.getMembersFromRequest().let { members ->
ac.assert { canUpdateMembers(workgroup, citizenOrNull) } ac.assert { canUpdateMembers(workgroup, citizenOrNull) }

View File

@@ -108,12 +108,22 @@ paths:
type: integer type: integer
401: 401:
$ref: '#/components/responses/401' $ref: '#/components/responses/401'
403:
description: Forbiden
content:
application/json:
schema:
description: Forbiden
properties:
statusCode:
type: integer
title:
type: string
/articles/{article}: /articles/{article}:
parameters: parameters:
- $ref: '#/components/parameters/article' - $ref: '#/components/parameters/article'
get: get:
security:
- JWTAuth: []
summary: Get one article summary: Get one article
tags: tags:
- article - article
@@ -1126,8 +1136,6 @@ paths:
/workgroups: /workgroups:
get: get:
summary: Get all Workgroup (Paginated) summary: Get all Workgroup (Paginated)
security:
- JWTAuth: [ ]
tags: tags:
- workgroup - workgroup
parameters: parameters:
@@ -1194,8 +1202,6 @@ paths:
- $ref: '#/components/parameters/workgroup' - $ref: '#/components/parameters/workgroup'
get: get:
summary: Get one workgroup by ID summary: Get one workgroup by ID
security:
- JWTAuth: [ ]
tags: tags:
- workgroup - workgroup
responses: responses:

View File

@@ -12,11 +12,13 @@ import integration.steps.given.`authenticated as`
import integration.steps.then.`And have property` import integration.steps.then.`And have property`
import integration.steps.then.`And the response should contain list` import integration.steps.then.`And the response should contain list`
import integration.steps.then.`And the response should contain pattern` import integration.steps.then.`And the response should contain pattern`
import integration.steps.then.`And the response should contain`
import integration.steps.then.`And the response should not be null` import integration.steps.then.`And the response should not be null`
import integration.steps.then.`And the response should not contain` import integration.steps.then.`And the response should not contain`
import integration.steps.then.`Then the response should be` import integration.steps.then.`Then the response should be`
import integration.steps.then.`whish contains` import integration.steps.then.`whish contains`
import integration.steps.then.and import integration.steps.then.and
import io.ktor.http.HttpStatusCode.Companion.Forbidden
import io.ktor.http.HttpStatusCode.Companion.OK import io.ktor.http.HttpStatusCode.Companion.OK
import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Tags
@@ -104,4 +106,30 @@ class `Article routes` : BaseTest() {
} }
} }
} }
@Test
fun `I cannot create an article if I'm not connected`() {
withIntegrationApplication {
`When I send a POST request`("/articles") {
`with body`(
"""
{
"versionId": "e3c7ce42-241c-4caf-9a59-aba4e466440e",
"title": "title2",
"anonymous": false,
"content": "content2",
"description": "description2",
"tags": [
"green"
]
}
"""
)
} `Then the response should be` Forbidden and {
`And the response should not be null`()
`And the response should contain`("$.statusCode", 403)
`And the response should contain`("$.title", "No User Connected")
}
}
}
} }

View File

@@ -0,0 +1,147 @@
package integration
import fr.dcproject.common.utils.getResource
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.openapi4j.core.model.OAIContext
import org.openapi4j.parser.OpenApi3Parser
import org.openapi4j.parser.model.v3.OpenApi3
import org.openapi4j.parser.model.v3.Operation
import org.openapi4j.parser.model.v3.Parameter
import org.openapi4j.parser.model.v3.Path
import java.io.File
import java.util.UUID
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("integration"), Tag("auth"))
class `Check auth on all routes` : BaseTest() {
@Test
fun `Check all routes`() {
val filePath = "/openapi.yaml"
OpenApi3Parser().parse(File(filePath.getResource().toURI()), true).let { api: OpenApi3 ->
/* Loop on paths and http methods */
api.paths.flatMap { (pathName: String, path: Path) ->
path.operations
/* Take only the secure route */
.filter { (_, operation: Operation) -> operation.hasSecurityRequirements() }
.map { (methodName, _) ->
/* Send request to check security */
sendRequest(
path.buildUrl(pathName, methodName, api.context), /* Replace route to real URL */
HttpMethod.parse(methodName.toUpperCase()) /* Convert http method name to enum */
)
}
}.let { requests ->
/* Check security of routes */
assertTrue(
requests.all { it.statusCode == HttpStatusCode.Forbidden },
requests
.filter { it.statusCode != HttpStatusCode.Forbidden }
.joinToString("\n") { it.toString() }
)
}
}
}
private fun sendRequest(uri: String, method: HttpMethod): RequestResponse {
return try {
withIntegrationApplication {
handleRequest(true) {
this.method = method
this.uri = uri
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
}.run {
RequestResponse(
response.status() ?: error("Request error"),
method,
uri
)
}
}
} catch (e: Throwable) {
RequestResponse(
HttpStatusCode.InternalServerError,
method,
uri
)
}
}
private data class RequestResponse(
val statusCode: HttpStatusCode,
val method: HttpMethod,
val uri: String
) {
override fun toString(): String {
return """HttpStatus ${statusCode.value} for: ${method.value.padStart(6, ' ')} $uri"""
}
}
}
private fun Path.buildUrl(path: String, methodName: String, context: OAIContext): String {
val urlReplaced = this.getParametersIn(context, "path")
.fold(path) { pathToReplace: String, parameter: Parameter ->
"""\{${parameter.name}}""".toRegex().replace(
pathToReplace,
parameter.generateFakeValue()
)
}
val rootQueryParameters = this.getParametersIn(context, "query")
.filter { it.isRequired }
.map { parameter ->
parameter
.generateFakeArray()
.joinToString("&") { "${parameter.name}=$it" }
}
val queryParameters = this.getOperation(methodName).getParametersIn(context, "query")
.filter { it.isRequired }
.map { parameter ->
parameter
.generateFakeArray()
.joinToString("&") { "${parameter.name}=$it" }
}
val allParameters: String = (rootQueryParameters + queryParameters)
.joinToString("&")
.let {
if (it.isNotEmpty()) {
"?$it"
} else {
it
}
}
return "$urlReplaced$allParameters"
}
private fun Parameter.generateFakeValue(): String {
return if (example != null) {
example.toString()
} else if (schema.type == "string" && schema.format == "uuid") {
UUID.randomUUID().toString()
} else {
"example123"
}
}
private fun Parameter.generateFakeArray(): List<String> {
if (schema.type != "array") {
error("Parameter is not an array")
}
return if (example != null && example is Iterable<*>) {
(example as Iterable<*>).map { it.toString() }
} else if (schema.itemsSchema.type == "string" && schema.itemsSchema.format == "uuid") {
listOf(UUID.randomUUID().toString())
} else {
listOf("example123")
}
}

View File

@@ -84,6 +84,7 @@ class `Comment articles routes` : BaseTest() {
`Given I have article`(id = "17df7fb9-b388-4e20-ab19-29c29972da01", createdBy = Name("Erwin", "Schrodinger")) `Given I have article`(id = "17df7fb9-b388-4e20-ab19-29c29972da01", createdBy = Name("Erwin", "Schrodinger"))
`Given I have comment on article`(article = "17df7fb9-b388-4e20-ab19-29c29972da01", createdBy = Name("Erwin", "Schrodinger")) `Given I have comment on article`(article = "17df7fb9-b388-4e20-ab19-29c29972da01", createdBy = Name("Erwin", "Schrodinger"))
`When I send a GET request`("/citizens/292a20cc-4a60-489e-9866-a95d38ffaf47/comments/articles") { `When I send a GET request`("/citizens/292a20cc-4a60-489e-9866-a95d38ffaf47/comments/articles") {
`authenticated as`("Erwin", "Schrodinger")
} `Then the response should be` OK and { } `Then the response should be` OK and {
`And the response should not be null`() `And the response should not be null`()
`And the response should contain`("$.currentPage", 1) `And the response should contain`("$.currentPage", 1)

View File

@@ -50,6 +50,7 @@ class `Comment constitutions routes` : BaseTest() {
`Given I have constitution`(id = "34ddd50a-da00-4a90-a869-08baa2a121be", createdBy = Name("Charles", "Darwin")) `Given I have constitution`(id = "34ddd50a-da00-4a90-a869-08baa2a121be", createdBy = Name("Charles", "Darwin"))
`Given I have comment on constitution`(constitution = "34ddd50a-da00-4a90-a869-08baa2a121be", createdBy = Name("Charles", "Darwin")) `Given I have comment on constitution`(constitution = "34ddd50a-da00-4a90-a869-08baa2a121be", createdBy = Name("Charles", "Darwin"))
`When I send a GET request`("/citizens/46e0bda9-ca6a-4c65-a58b-7e7267a0bbc5/comments/constitutions") { `When I send a GET request`("/citizens/46e0bda9-ca6a-4c65-a58b-7e7267a0bbc5/comments/constitutions") {
`authenticated as`("Charles", "Darwin")
} `Then the response should be` OK and { } `Then the response should be` OK and {
`And the response should not be null`() `And the response should not be null`()
`And the response should contain`("$.currentPage", 1) `And the response should contain`("$.currentPage", 1)

View File

@@ -63,8 +63,9 @@ fun TestApplicationResponse.`And the schema response body must be valid`(content
val schema = response.getContentMediaType(contentType.toString())?.schema val schema = response.getContentMediaType(contentType.toString())?.schema
if (content != null) { if (content != null) {
val httpMethod = call.request.httpMethod.value
schema?.validate(api, responseContent) schema?.validate(api, responseContent)
?: fail("""No Status "${status.value}" found with media type "$contentType" for "$this $uri".""") ?: fail("""No Status "${status.value}" found with media type "$contentType" for "$httpMethod $uri".""")
} }
} }
} }