Files
dc-project/src/test/kotlin/integration/Check auth on all routes.kt

148 lines
5.3 KiB
Kotlin

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