Merge pull request #84 from flecomte/69
Error codes
This commit was merged in pull request #84.
This commit is contained in:
@@ -6,17 +6,14 @@ import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
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.TEST
|
||||
import fr.dcproject.common.security.AccessDeniedException
|
||||
import fr.dcproject.application.http.statusPagesInstallation
|
||||
import fr.dcproject.component.article.articleKoinModule
|
||||
import fr.dcproject.component.article.routes.installArticleRoutes
|
||||
import fr.dcproject.component.auth.ForbiddenException
|
||||
import fr.dcproject.component.auth.authKoinModule
|
||||
import fr.dcproject.component.auth.jwt.jwtInstallation
|
||||
import fr.dcproject.component.auth.routes.installAuthRoutes
|
||||
import fr.dcproject.component.auth.user
|
||||
import fr.dcproject.component.citizen.citizenKoinModule
|
||||
import fr.dcproject.component.citizen.routes.installCitizenRoutes
|
||||
import fr.dcproject.component.comment.article.routes.installCommentArticleRoutes
|
||||
@@ -41,7 +38,6 @@ import fr.dcproject.component.workgroup.workgroupKoinModule
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.ApplicationStopped
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.client.HttpClient
|
||||
@@ -51,17 +47,14 @@ import io.ktor.features.CORS
|
||||
import io.ktor.features.CallLogging
|
||||
import io.ktor.features.ContentNegotiation
|
||||
import io.ktor.features.DataConversion
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.features.StatusPages
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.cio.websocket.pingPeriod
|
||||
import io.ktor.http.cio.websocket.timeout
|
||||
import io.ktor.jackson.jackson
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Locations
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Routing
|
||||
import io.ktor.server.jetty.EngineMain
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
@@ -73,7 +66,6 @@ import org.koin.ktor.ext.Koin
|
||||
import org.koin.ktor.ext.get
|
||||
import org.slf4j.event.Level
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletionException
|
||||
|
||||
fun main(args: Array<String>): Unit = EngineMain.main(args)
|
||||
|
||||
@@ -171,26 +163,7 @@ fun Application.module(env: Env = PROD) {
|
||||
installDocRoutes()
|
||||
}
|
||||
|
||||
install(StatusPages) {
|
||||
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(StatusPages, statusPagesInstallation())
|
||||
|
||||
install(CORS) {
|
||||
method(HttpMethod.Options)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRef
|
||||
@@ -54,6 +55,7 @@ object UpsertArticle {
|
||||
}
|
||||
|
||||
post<UpsertArticleRequest> {
|
||||
mustBeAuth()
|
||||
val article = call.convertRequestToEntity()
|
||||
ac.assert { canUpsert(article, citizenOrNull) }
|
||||
repo.upsert(article)?.let { a ->
|
||||
|
||||
@@ -26,7 +26,21 @@ val ApplicationCall.citizenOrNull: CitizenEntity?
|
||||
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>.citizenOrNull get() = context.citizenOrNull
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.database.UserRepository
|
||||
import fr.dcproject.component.auth.database.UserWithPassword
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import io.ktor.application.call
|
||||
@@ -29,6 +30,7 @@ object ChangeMyPassword {
|
||||
|
||||
fun Route.changeMyPassword(ac: CitizenAccessControl, userRepository: UserRepository) {
|
||||
put<ChangePasswordCitizenRequest> {
|
||||
mustBeAuth()
|
||||
ac.assert { canChangePassword(it.citizen, citizenOrNull) }
|
||||
val content = call.receiveOrBadRequest<ChangePasswordCitizenRequest.Input>()
|
||||
userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword)) ?: throw BadRequestException("Bad Password")
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.citizen.routes
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.component.citizen.database.CitizenCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
@@ -30,6 +31,7 @@ object FindCitizens {
|
||||
|
||||
fun Route.findCitizen(ac: CitizenAccessControl, repo: CitizenRepository) {
|
||||
get<CitizensRequest> {
|
||||
mustBeAuth()
|
||||
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
|
||||
ac.assert { canView(citizens.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.citizen.routes
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -22,6 +23,7 @@ object GetCurrentCitizen {
|
||||
|
||||
fun Route.getCurrentCitizen(ac: CitizenAccessControl) {
|
||||
get<CurrentCitizenRequest> {
|
||||
mustBeAuth()
|
||||
val currentUser = citizenOrNull
|
||||
if (currentUser === null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.citizen.routes
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
@@ -26,6 +27,7 @@ object GetOneCitizen {
|
||||
|
||||
fun Route.getOneCitizen(ac: CitizenAccessControl, citizenRepository: CitizenRepository) {
|
||||
get<CitizenRequest> {
|
||||
mustBeAuth()
|
||||
val citizen = citizenRepository.findById(it.citizen.id) ?: throw NotFoundException("Citizen not found ${it.citizen.id}")
|
||||
ac.assert { canView(citizen, citizenOrNull) }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.routes.CreateCommentArticle.PostArticleCommentRequest.Input
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
@@ -30,6 +31,7 @@ object CreateCommentArticle {
|
||||
|
||||
fun Route.createCommentArticle(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
post<PostArticleCommentRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
CommentForUpdate(
|
||||
target = it.article,
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.comment.article.routes
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.comment.article.database.CommentArticleRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
@@ -25,6 +26,7 @@ object GetCitizenArticleComments {
|
||||
|
||||
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
get<CitizenCommentArticleRequest> {
|
||||
mustBeAuth()
|
||||
repo.findByCitizen(it.citizen).let { comments ->
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -5,6 +5,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.routes.CreateConstitutionComment.CreateConstitutionCommentRequest.Input
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
@@ -30,6 +31,7 @@ object CreateConstitutionComment {
|
||||
|
||||
fun Route.createConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
post<CreateConstitutionCommentRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
CommentForUpdate(
|
||||
target = it.constitution,
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.comment.constitution.routes
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.comment.constitution.database.CommentConstitutionRepository
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
@@ -25,6 +26,7 @@ object GetCitizenCommentConstitution {
|
||||
|
||||
fun Route.getCitizenCommentConstitution(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
get<GetCitizenCommentConstitutionRequest> {
|
||||
mustBeAuth()
|
||||
val comments = repo.findByCitizen(it.citizen)
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
@@ -29,6 +30,7 @@ object CreateCommentChildren {
|
||||
|
||||
fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
post<CreateCommentChildrenRequest> {
|
||||
mustBeAuth()
|
||||
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
val newComment = CommentForUpdate(
|
||||
content = call.receiveOrBadRequest<CreateCommentChildrenRequest.Input>().content,
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
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.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
@@ -28,6 +29,7 @@ object EditComment {
|
||||
|
||||
fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
put<EditCommentRequest> {
|
||||
mustBeAuth()
|
||||
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
ac.assert { canUpdate(comment, citizenOrNull) }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.CitizenWithUserI
|
||||
import fr.dcproject.component.constitution.ConstitutionAccessControl
|
||||
@@ -68,6 +69,7 @@ object CreateConstitution {
|
||||
|
||||
fun Route.createConstitution(repo: ConstitutionRepository, ac: ConstitutionAccessControl) {
|
||||
post<PostConstitutionRequest> {
|
||||
mustBeAuth()
|
||||
getNewConstitution(call.receiveOrBadRequest(), citizen).let {
|
||||
ac.assert { canCreate(it, citizenOrNull) }
|
||||
val c = repo.upsert(it) ?: error("Unable to create Constitution")
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||
@@ -25,6 +26,7 @@ object FollowArticle {
|
||||
|
||||
fun Route.followArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
post<ArticleFollowRequest> {
|
||||
mustBeAuth()
|
||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||
ac.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.article
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
@@ -25,6 +26,7 @@ object GetMyFollowsArticle {
|
||||
|
||||
fun Route.getMyFollowsArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
get<CitizenFollowArticleRequest> {
|
||||
mustBeAuth()
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
ac.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||
@@ -25,6 +26,7 @@ object UnfollowArticle {
|
||||
|
||||
fun Route.unfollowArticle(repo: FollowArticleRepository, ac: FollowAccessControl) {
|
||||
delete<ArticleFollowRequest> {
|
||||
mustBeAuth()
|
||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||
ac.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
@@ -25,6 +26,7 @@ object FollowConstitution {
|
||||
|
||||
fun Route.followConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
post<ConstitutionFollowRequest> {
|
||||
mustBeAuth()
|
||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||
ac.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
@@ -25,6 +26,7 @@ object GetMyFollowsConstitution {
|
||||
|
||||
fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
get<CitizenFollowConstitutionRequest> {
|
||||
mustBeAuth()
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
ac.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.follow.routes.constitution
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
@@ -25,6 +26,7 @@ object UnfollowConstitution {
|
||||
|
||||
fun Route.unfollowConstitution(repo: FollowConstitutionRepository, ac: FollowAccessControl) {
|
||||
delete<ConstitutionUnfollowRequest> {
|
||||
mustBeAuth()
|
||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||
ac.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
|
||||
@@ -5,6 +5,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.opinion.OpinionAccessControl
|
||||
import fr.dcproject.component.opinion.database.Opinion
|
||||
@@ -31,6 +32,7 @@ object GetCitizenOpinions {
|
||||
|
||||
fun Route.getCitizenOpinions(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
get<CitizenOpinions> {
|
||||
mustBeAuth()
|
||||
val opinionsEntities: List<Opinion<ArticleRef>> = repo.findCitizenOpinionsByTargets(it.citizen, it.id)
|
||||
ac.assert { canView(opinionsEntities, citizenOrNull) }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.opinion.OpinionAccessControl
|
||||
import fr.dcproject.component.opinion.database.Opinion
|
||||
@@ -37,6 +38,7 @@ object GetMyOpinionsArticle {
|
||||
|
||||
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
get<CitizenOpinionsArticleRequest> {
|
||||
mustBeAuth()
|
||||
val opinions: Paginated<Opinion<TargetRef>> = repo.findCitizenOpinions(citizen, it.page, it.limit)
|
||||
ac.assert { canView(opinions.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.opinion.OpinionAccessControl
|
||||
import fr.dcproject.component.opinion.database.OpinionChoiceRef
|
||||
import fr.dcproject.component.opinion.database.OpinionForUpdate
|
||||
@@ -34,6 +35,7 @@ object OpinionArticle {
|
||||
|
||||
fun Route.setOpinionOnArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
put<ArticleOpinion> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<ArticleOpinion.Body>().ids.map { id ->
|
||||
OpinionForUpdate(
|
||||
choice = OpinionChoiceRef(id),
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteRepository
|
||||
@@ -26,6 +27,7 @@ object GetCitizenVotes {
|
||||
|
||||
fun Route.getCitizenVote(repo: VoteRepository, ac: VoteAccessControl) {
|
||||
get<CitizenVotesRequest> {
|
||||
mustBeAuth()
|
||||
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
|
||||
if (votes.isNotEmpty()) {
|
||||
ac.assert { canView(votes, citizenOrNull) }
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.vote.routes
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteArticleRepository
|
||||
@@ -31,6 +32,7 @@ object GetCitizenVotesOnArticle {
|
||||
|
||||
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
|
||||
get<CitizenVoteArticleRequest> {
|
||||
mustBeAuth()
|
||||
val votes = repo.findByCitizen(it.citizen, it.page, it.limit)
|
||||
ac.assert { canView(votes.result, citizenOrNull) }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteArticleRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
@@ -29,6 +30,7 @@ object PutVoteOnArticle {
|
||||
|
||||
fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) {
|
||||
put<ArticleVoteRequest> {
|
||||
mustBeAuth()
|
||||
val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>()
|
||||
val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
|
||||
val vote = VoteForUpdate(
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteCommentRepository
|
||||
@@ -26,6 +27,7 @@ object PutVoteOnComment {
|
||||
|
||||
fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, ac: VoteAccessControl) {
|
||||
put<CommentVoteRequest> {
|
||||
mustBeAuth()
|
||||
val comment = commentRepo.findById(it.comment)!!
|
||||
val content = call.receiveOrBadRequest<CommentVoteRequest.Content>()
|
||||
val vote = VoteForUpdate(
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
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.ConstitutionRepository
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
@@ -30,6 +31,7 @@ object PutVoteOnConstitution {
|
||||
|
||||
fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl, constitutionRepo: ConstitutionRepository) {
|
||||
put<ConstitutionVoteRequest> {
|
||||
mustBeAuth()
|
||||
val constitution = constitutionRepo.findById(it.constitution.id) ?: throw NotFoundException("Unable to find constitution ${it.constitution.id}")
|
||||
val content = call.receiveOrBadRequest<Input>()
|
||||
val vote = VoteForUpdate(
|
||||
|
||||
@@ -5,6 +5,7 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
@@ -33,6 +34,7 @@ object CreateWorkgroup {
|
||||
|
||||
fun Route.createWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
post<PostWorkgroupRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
WorkgroupForUpdate(
|
||||
id ?: UUID.randomUUID(),
|
||||
|
||||
@@ -2,6 +2,7 @@ package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
import io.ktor.application.call
|
||||
@@ -20,6 +21,7 @@ object DeleteWorkgroup {
|
||||
|
||||
fun Route.deleteWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
delete<DeleteWorkgroupRequest> {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
ac.assert { canDelete(workgroup, citizenOrNull) }
|
||||
repo.delete(workgroup)
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
@@ -31,6 +32,7 @@ object EditWorkgroup {
|
||||
|
||||
fun Route.editWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
put<PutWorkgroupRequest> {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { old ->
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
WorkgroupForUpdate(
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes.members
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
@@ -44,6 +45,7 @@ object AddMemberToWorkgroup {
|
||||
fun Route.addMemberToWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
/* Add members to workgroup */
|
||||
post<WorkgroupsMembersRequest> {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
call.getMembersFromRequest().let { members ->
|
||||
ac.assert { canAddMembers(workgroup, citizenOrNull) }
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes.members
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
@@ -35,6 +36,7 @@ object DeleteMembersOfWorkgroup {
|
||||
fun Route.deleteMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
/* Delete members of workgroup */
|
||||
delete<WorkgroupsMembersRequest> {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
call.getMembersFromRequest()
|
||||
.let { members ->
|
||||
|
||||
@@ -3,6 +3,7 @@ package fr.dcproject.component.workgroup.routes.members
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
@@ -42,6 +43,7 @@ object UpdateMemberOfWorkgroup {
|
||||
fun Route.updateMemberOfWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
/* Update members of workgroup */
|
||||
put<WorkgroupsMembersRequest> {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { workgroup ->
|
||||
call.getMembersFromRequest().let { members ->
|
||||
ac.assert { canUpdateMembers(workgroup, citizenOrNull) }
|
||||
|
||||
@@ -108,12 +108,22 @@ paths:
|
||||
type: integer
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
403:
|
||||
description: Forbiden
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: Forbiden
|
||||
properties:
|
||||
statusCode:
|
||||
type: integer
|
||||
title:
|
||||
type: string
|
||||
|
||||
/articles/{article}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/article'
|
||||
get:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
summary: Get one article
|
||||
tags:
|
||||
- article
|
||||
@@ -1126,8 +1136,6 @@ paths:
|
||||
/workgroups:
|
||||
get:
|
||||
summary: Get all Workgroup (Paginated)
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
tags:
|
||||
- workgroup
|
||||
parameters:
|
||||
@@ -1194,8 +1202,6 @@ paths:
|
||||
- $ref: '#/components/parameters/workgroup'
|
||||
get:
|
||||
summary: Get one workgroup by ID
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
tags:
|
||||
- workgroup
|
||||
responses:
|
||||
|
||||
@@ -12,11 +12,13 @@ import integration.steps.given.`authenticated as`
|
||||
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 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 contain`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`whish contains`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.Forbidden
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
147
src/test/kotlin/integration/Check auth on all routes.kt
Normal file
147
src/test/kotlin/integration/Check auth on all routes.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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 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") {
|
||||
`authenticated as`("Erwin", "Schrodinger")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.currentPage", 1)
|
||||
|
||||
@@ -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 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") {
|
||||
`authenticated as`("Charles", "Darwin")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.currentPage", 1)
|
||||
|
||||
@@ -63,8 +63,9 @@ fun TestApplicationResponse.`And the schema response body must be valid`(content
|
||||
val schema = response.getContentMediaType(contentType.toString())?.schema
|
||||
|
||||
if (content != null) {
|
||||
val httpMethod = call.request.httpMethod.value
|
||||
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".""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user