feat: auto auth detect (#299)
This commit is contained in:
@ -15,6 +15,13 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [3.1.0] - August 18th, 2022
|
||||||
|
### Added
|
||||||
|
- Ability to automatically detect authentication via route
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Improved stack trace output
|
||||||
|
|
||||||
## [3.0.0] - August 16th, 2022
|
## [3.0.0] - August 16th, 2022
|
||||||
### Added
|
### Added
|
||||||
- Ktor 2 Support 🎉
|
- Ktor 2 Support 🎉
|
||||||
|
@ -49,6 +49,12 @@ dependencies {
|
|||||||
testFixturesApi("io.ktor:ktor-serialization-gson:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-serialization-gson:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
testFixturesApi("io.ktor:ktor-server-content-negotiation:$ktorVersion")
|
||||||
|
testFixturesApi("io.ktor:ktor-server-auth:$ktorVersion")
|
||||||
|
testFixturesApi("io.ktor:ktor-server-auth-jwt:$ktorVersion")
|
||||||
|
testFixturesApi("io.ktor:ktor-client:$ktorVersion")
|
||||||
|
testFixturesApi("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
|
|
||||||
|
testFixturesApi("dev.forst:ktor-api-key:2.1.0")
|
||||||
|
|
||||||
testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
|
testFixturesApi("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import io.bkbn.kompendium.core.metadata.PutInfo
|
|||||||
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
import io.bkbn.kompendium.core.util.Helpers.addToSpec
|
||||||
import io.bkbn.kompendium.core.util.SpecConfig
|
import io.bkbn.kompendium.core.util.SpecConfig
|
||||||
import io.bkbn.kompendium.oas.path.Path
|
import io.bkbn.kompendium.oas.path.Path
|
||||||
|
import io.bkbn.kompendium.oas.path.PathOperation
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.ktor.server.application.ApplicationCallPipeline
|
import io.ktor.server.application.ApplicationCallPipeline
|
||||||
import io.ktor.server.application.Hook
|
import io.ktor.server.application.Hook
|
||||||
@ -44,11 +45,13 @@ object NotarizedRoute {
|
|||||||
createConfiguration = ::Config
|
createConfiguration = ::Config
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// This is required in order to introspect the route path
|
// This is required in order to introspect the route path and authentication
|
||||||
on(InstallHook) {
|
on(InstallHook) {
|
||||||
val route = it as? Route ?: return@on
|
val route = it as? Route ?: return@on
|
||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
val routePath = route.calculateRoutePath()
|
val routePath = route.calculateRoutePath()
|
||||||
|
val authMethods = route.collectAuthMethods()
|
||||||
|
pluginConfig.path?.addDefaultAuthMethods(authMethods)
|
||||||
require(spec.paths[routePath] == null) {
|
require(spec.paths[routePath] == null) {
|
||||||
"""
|
"""
|
||||||
The specified path ${Parameter.Location.path} has already been documented!
|
The specified path ${Parameter.Location.path} has already been documented!
|
||||||
@ -76,4 +79,33 @@ object NotarizedRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
private fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||||
|
private fun Route.collectAuthMethods() = toString()
|
||||||
|
.split("/")
|
||||||
|
.filter { it.contains(Regex("\\(authenticate .*\\)")) }
|
||||||
|
.map { it.replace("(authenticate ", "").replace(")", "") }
|
||||||
|
.map { it.split(", ") }
|
||||||
|
.flatten()
|
||||||
|
|
||||||
|
private fun Path.addDefaultAuthMethods(methods: List<String>) {
|
||||||
|
get?.addDefaultAuthMethods(methods)
|
||||||
|
put?.addDefaultAuthMethods(methods)
|
||||||
|
post?.addDefaultAuthMethods(methods)
|
||||||
|
delete?.addDefaultAuthMethods(methods)
|
||||||
|
options?.addDefaultAuthMethods(methods)
|
||||||
|
head?.addDefaultAuthMethods(methods)
|
||||||
|
patch?.addDefaultAuthMethods(methods)
|
||||||
|
trace?.addDefaultAuthMethods(methods)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PathOperation.addDefaultAuthMethods(methods: List<String>) {
|
||||||
|
methods.forEach { m ->
|
||||||
|
if (security == null || security?.all { s -> !s.containsKey(m) } == true) {
|
||||||
|
if (security == null) {
|
||||||
|
security = mutableListOf(mapOf(m to emptyList()))
|
||||||
|
} else {
|
||||||
|
security?.add(mapOf(m to emptyList()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ object Helpers {
|
|||||||
security = config.security
|
security = config.security
|
||||||
?.map { (k, v) -> k to v }
|
?.map { (k, v) -> k to v }
|
||||||
?.map { listOf(it).toMap() }
|
?.map { listOf(it).toMap() }
|
||||||
?.toList(),
|
?.toMutableList(),
|
||||||
requestBody = when (this) {
|
requestBody = when (this) {
|
||||||
is MethodInfoWithRequest -> Request(
|
is MethodInfoWithRequest -> Request(
|
||||||
description = this.request.description,
|
description = this.request.description,
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package io.bkbn.kompendium.core
|
package io.bkbn.kompendium.core
|
||||||
|
|
||||||
|
import dev.forst.ktor.apikey.apiKey
|
||||||
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
||||||
import io.bkbn.kompendium.core.util.TestModules.complexRequest
|
import io.bkbn.kompendium.core.util.TestModules.complexRequest
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.customAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.dateTimeString
|
import io.bkbn.kompendium.core.util.TestModules.dateTimeString
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.defaultAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultField
|
import io.bkbn.kompendium.core.util.TestModules.defaultField
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultParameter
|
import io.bkbn.kompendium.core.util.TestModules.defaultParameter
|
||||||
import io.bkbn.kompendium.core.util.TestModules.exampleParams
|
import io.bkbn.kompendium.core.util.TestModules.exampleParams
|
||||||
@ -16,6 +19,7 @@ import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
||||||
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.multipleAuthStrategies
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection
|
import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection
|
||||||
@ -46,7 +50,22 @@ import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
||||||
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
|
import io.bkbn.kompendium.oas.component.Components
|
||||||
|
import io.bkbn.kompendium.oas.security.ApiKeyAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.BearerAuth
|
||||||
|
import io.bkbn.kompendium.oas.security.OAuth
|
||||||
import io.kotest.core.spec.style.DescribeSpec
|
import io.kotest.core.spec.style.DescribeSpec
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.cio.CIO
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.Authentication
|
||||||
|
import io.ktor.server.auth.OAuthServerSettings
|
||||||
|
import io.ktor.server.auth.UserIdPrincipal
|
||||||
|
import io.ktor.server.auth.basic
|
||||||
|
import io.ktor.server.auth.jwt.jwt
|
||||||
|
import io.ktor.server.auth.oauth
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@ -190,7 +209,7 @@ class KompendiumTest : DescribeSpec({
|
|||||||
// TODO Assess strategies here
|
// TODO Assess strategies here
|
||||||
}
|
}
|
||||||
it("Can serialize a recursive type") {
|
it("Can serialize a recursive type") {
|
||||||
openApiTestAllSerializers("T0042__simple_recursive.json") { simpleRecursive() }
|
openApiTestAllSerializers("T0042__simple_recursive.json") { simpleRecursive() }
|
||||||
}
|
}
|
||||||
it("Nullable fields do not lead to doom") {
|
it("Nullable fields do not lead to doom") {
|
||||||
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }
|
openApiTestAllSerializers("T0036__nullable_fields.json") { nullableNestedObject() }
|
||||||
@ -219,4 +238,100 @@ class KompendiumTest : DescribeSpec({
|
|||||||
describe("Free Form") {
|
describe("Free Form") {
|
||||||
// todo Assess strategies here
|
// todo Assess strategies here
|
||||||
}
|
}
|
||||||
|
describe("Authentication") {
|
||||||
|
it("Can add a default auth config by default") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0045__default_auth_config.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
basic("basic") {
|
||||||
|
realm = "Ktor Server"
|
||||||
|
validate { UserIdPrincipal("Placeholder") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"basic" to BasicAuth()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { defaultAuthConfig() }
|
||||||
|
}
|
||||||
|
it("Can provide custom auth config with proper scopes") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0046__custom_auth_config.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
oauth("auth-oauth-google") {
|
||||||
|
urlProvider = { "http://localhost:8080/callback" }
|
||||||
|
providerLookup = {
|
||||||
|
OAuthServerSettings.OAuth2ServerSettings(
|
||||||
|
name = "google",
|
||||||
|
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
|
||||||
|
requestMethod = HttpMethod.Post,
|
||||||
|
clientId = "DUMMY_VAL",
|
||||||
|
clientSecret = "DUMMY_VAL",
|
||||||
|
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
|
||||||
|
extraTokenParameters = listOf("access_type" to "offline")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
client = HttpClient(CIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"auth-oauth-google" to OAuth(
|
||||||
|
flows = OAuth.Flows(
|
||||||
|
implicit = OAuth.Flows.Implicit(
|
||||||
|
authorizationUrl = "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
scopes = mapOf(
|
||||||
|
"write:pets" to "modify pets in your account",
|
||||||
|
"read:pets" to "read your pets"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { customAuthConfig() }
|
||||||
|
}
|
||||||
|
it("Can provide multiple authentication strategies") {
|
||||||
|
openApiTestAllSerializers(
|
||||||
|
snapshotName = "T0047__multiple_auth_strategies.json",
|
||||||
|
applicationSetup = {
|
||||||
|
install(Authentication) {
|
||||||
|
apiKey("api-key") {
|
||||||
|
headerName = "X-API-KEY"
|
||||||
|
validate {
|
||||||
|
UserIdPrincipal("Placeholder")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jwt("jwt") {
|
||||||
|
realm = "Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specOverrides = {
|
||||||
|
this.copy(
|
||||||
|
components = Components(
|
||||||
|
securitySchemes = mutableMapOf(
|
||||||
|
"jwt" to BearerAuth("JWT"),
|
||||||
|
"api-key" to ApiKeyAuth(ApiKeyAuth.ApiKeyLocation.HEADER, "X-API-KEY")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { multipleAuthStrategies() }
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -35,8 +35,10 @@ import io.bkbn.kompendium.oas.payload.Parameter
|
|||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.auth.authenticate
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
import io.ktor.server.response.respondText
|
import io.ktor.server.response.respondText
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
import io.ktor.server.routing.Routing
|
import io.ktor.server.routing.Routing
|
||||||
import io.ktor.server.routing.delete
|
import io.ktor.server.routing.delete
|
||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
@ -604,24 +606,68 @@ object TestModules {
|
|||||||
|
|
||||||
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
|
||||||
|
|
||||||
private inline fun <reified T> Routing.basicGetGenerator(
|
fun Routing.defaultAuthConfig() {
|
||||||
params: List<Parameter> = emptyList(),
|
authenticate("basic") {
|
||||||
operationId: String? = null
|
route(rootPath) {
|
||||||
) {
|
basicGetGenerator<TestResponse>()
|
||||||
route(rootPath) {
|
}
|
||||||
install(NotarizedRoute()) {
|
}
|
||||||
get = GetInfo.builder {
|
}
|
||||||
summary(defaultPathSummary)
|
|
||||||
description(defaultPathDescription)
|
fun Routing.customAuthConfig() {
|
||||||
operationId?.let { operationId(it) }
|
authenticate("auth-oauth-google") {
|
||||||
parameters = params
|
route(rootPath) {
|
||||||
response {
|
install(NotarizedRoute()) {
|
||||||
description(defaultResponseDescription)
|
get = GetInfo.builder {
|
||||||
responseCode(HttpStatusCode.OK)
|
summary(defaultPathSummary)
|
||||||
responseType<T>()
|
description(defaultPathDescription)
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<TestResponse>()
|
||||||
|
}
|
||||||
|
security = mapOf(
|
||||||
|
"auth-oauth-google" to listOf("read:pets")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Routing.multipleAuthStrategies() {
|
||||||
|
authenticate("jwt", "api-key") {
|
||||||
|
route(rootPath) {
|
||||||
|
basicGetGenerator<TestResponse>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> Routing.basicGetGenerator(
|
||||||
|
params: List<Parameter> = emptyList(),
|
||||||
|
operationId: String? = null
|
||||||
|
) {
|
||||||
|
route(rootPath) {
|
||||||
|
basicGetGenerator<T>(params, operationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> Route.basicGetGenerator(
|
||||||
|
params: List<Parameter> = emptyList(),
|
||||||
|
operationId: String? = null
|
||||||
|
) {
|
||||||
|
install(NotarizedRoute()) {
|
||||||
|
get = GetInfo.builder {
|
||||||
|
summary(defaultPathSummary)
|
||||||
|
description(defaultPathDescription)
|
||||||
|
operationId?.let { operationId(it) }
|
||||||
|
parameters = params
|
||||||
|
response {
|
||||||
|
description(defaultResponseDescription)
|
||||||
|
responseCode(HttpStatusCode.OK)
|
||||||
|
responseType<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
82
core/src/test/resources/T0045__default_auth_config.json
Normal file
82
core/src/test/resources/T0045__default_auth_config.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"basic": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "basic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
92
core/src/test/resources/T0046__custom_auth_config.json
Normal file
92
core/src/test/resources/T0046__custom_auth_config.json
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"auth-oauth-google": [
|
||||||
|
"read:pets"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"auth-oauth-google": {
|
||||||
|
"flows": {
|
||||||
|
"implicit": {
|
||||||
|
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"scopes": {
|
||||||
|
"write:pets": "modify pets in your account",
|
||||||
|
"read:pets": "read your pets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "oauth2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
91
core/src/test/resources/T0047__multiple_auth_strategies.json
Normal file
91
core/src/test/resources/T0047__multiple_auth_strategies.json
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"info": {
|
||||||
|
"title": "Test API",
|
||||||
|
"version": "1.33.7",
|
||||||
|
"description": "An amazing, fully-ish 😉 generated API spec",
|
||||||
|
"termsOfService": "https://example.com",
|
||||||
|
"contact": {
|
||||||
|
"name": "Homer Simpson",
|
||||||
|
"url": "https://gph.is/1NPUDiM",
|
||||||
|
"email": "chunkylover53@aol.com"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT",
|
||||||
|
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://myawesomeapi.com",
|
||||||
|
"description": "Production instance of my API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://staging.myawesomeapi.com",
|
||||||
|
"description": "Where the fun stuff happens"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false,
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"jwt": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"api-key": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TestResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"c": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {
|
||||||
|
"jwt": {
|
||||||
|
"bearerFormat": "JWT",
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer"
|
||||||
|
},
|
||||||
|
"api-key": {
|
||||||
|
"in": "header",
|
||||||
|
"name": "X-API-KEY",
|
||||||
|
"type": "apiKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -59,17 +59,17 @@ object TestHelpers {
|
|||||||
* and build a test ktor server to compare the expected output with the output found in the default
|
* and build a test ktor server to compare the expected output with the output found in the default
|
||||||
* OpenAPI json endpoint. By default, this will run the same test with Gson, Kotlinx, and Jackson serializers
|
* OpenAPI json endpoint. By default, this will run the same test with Gson, Kotlinx, and Jackson serializers
|
||||||
* @param snapshotName The snapshot file to retrieve from the resources folder
|
* @param snapshotName The snapshot file to retrieve from the resources folder
|
||||||
* @param moduleFunction Initializer for the application to allow tests to pass the required Ktor modules
|
|
||||||
*/
|
*/
|
||||||
fun openApiTestAllSerializers(
|
fun openApiTestAllSerializers(
|
||||||
snapshotName: String,
|
snapshotName: String,
|
||||||
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
customTypes: Map<KType, JsonSchema> = emptyMap(),
|
||||||
applicationSetup: Application.() -> Unit = { },
|
applicationSetup: Application.() -> Unit = { },
|
||||||
|
specOverrides: OpenApiSpec.() -> OpenApiSpec = { this },
|
||||||
routeUnderTest: Routing.() -> Unit
|
routeUnderTest: Routing.() -> Unit
|
||||||
) {
|
) {
|
||||||
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, applicationSetup, customTypes)
|
openApiTest(snapshotName, SupportedSerializer.KOTLINX, routeUnderTest, applicationSetup, specOverrides, customTypes)
|
||||||
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, applicationSetup, customTypes)
|
openApiTest(snapshotName, SupportedSerializer.JACKSON, routeUnderTest, applicationSetup, specOverrides, customTypes)
|
||||||
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, applicationSetup, customTypes)
|
openApiTest(snapshotName, SupportedSerializer.GSON, routeUnderTest, applicationSetup, specOverrides, customTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openApiTest(
|
private fun openApiTest(
|
||||||
@ -77,11 +77,12 @@ object TestHelpers {
|
|||||||
serializer: SupportedSerializer,
|
serializer: SupportedSerializer,
|
||||||
routeUnderTest: Routing.() -> Unit,
|
routeUnderTest: Routing.() -> Unit,
|
||||||
applicationSetup: Application.() -> Unit,
|
applicationSetup: Application.() -> Unit,
|
||||||
|
specOverrides: OpenApiSpec.() -> OpenApiSpec,
|
||||||
typeOverrides: Map<KType, JsonSchema> = emptyMap()
|
typeOverrides: Map<KType, JsonSchema> = emptyMap()
|
||||||
) = testApplication {
|
) = testApplication {
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
customTypes = typeOverrides
|
customTypes = typeOverrides
|
||||||
spec = defaultSpec()
|
spec = defaultSpec().specOverrides()
|
||||||
}
|
}
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
when (serializer) {
|
when (serializer) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=3.0.0
|
project.version=3.1.0
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
@ -39,6 +39,6 @@ data class PathOperation(
|
|||||||
var responses: Map<Int, Response>? = null,
|
var responses: Map<Int, Response>? = null,
|
||||||
var callbacks: Map<String, PathOperation>? = null,
|
var callbacks: Map<String, PathOperation>? = null,
|
||||||
var deprecated: Boolean = false,
|
var deprecated: Boolean = false,
|
||||||
var security: List<Map<String, List<String>>>? = null,
|
var security: MutableList<Map<String, List<String>>>? = null,
|
||||||
var servers: List<Server>? = null,
|
var servers: List<Server>? = null,
|
||||||
)
|
)
|
||||||
|
@ -92,9 +92,6 @@ private fun Route.locationDocumentation() {
|
|||||||
get = GetInfo.builder {
|
get = GetInfo.builder {
|
||||||
summary("Get user by id")
|
summary("Get user by id")
|
||||||
description("A very neat endpoint!")
|
description("A very neat endpoint!")
|
||||||
security = mapOf(
|
|
||||||
"basic" to emptyList()
|
|
||||||
)
|
|
||||||
response {
|
response {
|
||||||
responseCode(HttpStatusCode.OK)
|
responseCode(HttpStatusCode.OK)
|
||||||
responseType<ExampleResponse>()
|
responseType<ExampleResponse>()
|
||||||
|
Reference in New Issue
Block a user