init security scheme (#29)
This commit is contained in:
@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [0.6.0] - April 21st, 2021
|
||||
|
||||
### Added
|
||||
|
||||
- Added basic and jwt security scheme support with the new module kompendium-auth
|
||||
|
||||
## [0.5.2] - April 19th, 2021
|
||||
|
||||
### Removed
|
||||
|
50
README.md
50
README.md
@ -41,7 +41,6 @@ dependencies {
|
||||
Kompendium is still under active development ⚠️ There are a number of yet-to-be-implemented features, including
|
||||
|
||||
- Multiple Responses 📜
|
||||
- Security Schemas 🔏
|
||||
- Sealed Class / Polymorphic Support 😬
|
||||
- Validation / Enforcement (❓👀❓)
|
||||
|
||||
@ -135,6 +134,55 @@ When run in the playground, this would output the following at `/openapi.json`
|
||||
|
||||
https://gist.github.com/rgbrizzlehizzle/b9544922f2e99a2815177f8bdbf80668
|
||||
|
||||
### Kompendium Auth and security schemes
|
||||
|
||||
There is a seperate library to handle security schemes: `kompendium-auth`.
|
||||
This needs to be added to your project as dependency.
|
||||
|
||||
At the moment, the basic and jwt authentication is only supported.
|
||||
|
||||
A minimal example would be:
|
||||
```kotlin
|
||||
install(Authentication) {
|
||||
notarizedBasic("basic") {
|
||||
realm = "Ktor realm 1"
|
||||
// ...
|
||||
}
|
||||
notarizedJwt("jwt") {
|
||||
realm = "Ktor realm 2"
|
||||
// ...
|
||||
}
|
||||
}
|
||||
routing {
|
||||
authenticate("basic") {
|
||||
route("/basic_auth") {
|
||||
notarizedGet<TestParams, TestResponse>(
|
||||
MethodInfo(
|
||||
// securitySchemes needs to be set
|
||||
"Another get test", "testing more", testGetResponse, securitySchemes = setOf("basic")
|
||||
)
|
||||
) {
|
||||
call.respondText { "basic auth" }
|
||||
}
|
||||
}
|
||||
}
|
||||
authenticate("jwt") {
|
||||
route("/jwt") {
|
||||
notarizedGet<TestParams, TestResponse>(
|
||||
MethodInfo(
|
||||
// securitySchemes needs to be set
|
||||
"Another get test", "testing more", testGetResponse, securitySchemes = setOf("jwt")
|
||||
)
|
||||
) {
|
||||
call.respondText { "jwt" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
### Kompendium as a singleton
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Kompendium
|
||||
project.version=0.5.2
|
||||
project.version=0.6.0
|
||||
# Kotlin
|
||||
kotlin.code.style=official
|
||||
# Gradle
|
||||
|
@ -10,6 +10,9 @@ ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref =
|
||||
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
|
||||
ktor-jackson = { group = "io.ktor", name = "ktor-jackson", version.ref = "ktor" }
|
||||
ktor-html-builder = { group = "io.ktor", name = "ktor-html-builder", version.ref = "ktor" }
|
||||
ktor-auth-lib = { group = "io.ktor", name = "ktor-auth", version.ref = "ktor" }
|
||||
ktor-auth-jwt = { group = "io.ktor", name = "ktor-auth-jwt", version.ref = "ktor" }
|
||||
|
||||
# Logging
|
||||
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
|
||||
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" }
|
||||
@ -17,4 +20,5 @@ logback-core = { group = "ch.qos.logback", name = "logback-core", version.ref =
|
||||
|
||||
[bundles]
|
||||
ktor = [ "ktor-server-core", "ktor-server-netty", "ktor-jackson", "ktor-html-builder" ]
|
||||
ktorAuth = [ "ktor-auth-lib", "ktor-auth-jwt" ]
|
||||
logging = [ "slf4j", "logback-classic", "logback-core" ]
|
||||
|
40
kompendium-auth/build.gradle.kts
Normal file
40
kompendium-auth/build.gradle.kts
Normal file
@ -0,0 +1,40 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.ktorAuth)
|
||||
implementation(projects.kompendiumCore)
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
|
||||
testImplementation("io.ktor:ktor-server-test-host:1.5.3")
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GithubPackages"
|
||||
url = uri("https://maven.pkg.github.com/lg-backbone/kompendium")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("kompendium") {
|
||||
from(components["kotlin"])
|
||||
artifact(tasks.sourcesJar)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.leafygreens.kompendium.auth
|
||||
|
||||
import io.ktor.auth.AuthenticationRouteSelector
|
||||
import io.ktor.routing.Route
|
||||
import org.leafygreens.kompendium.path.CorePathCalculator
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class AuthPathCalculator : CorePathCalculator() {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
override fun handleCustomSelectors(route: Route, tail: String): String = when (route.selector) {
|
||||
is AuthenticationRouteSelector -> {
|
||||
logger.debug("Found authentication route selector ${route.selector}")
|
||||
super.calculate(route.parent, tail)
|
||||
}
|
||||
else -> super.handleCustomSelectors(route, tail)
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package org.leafygreens.kompendium.auth
|
||||
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.auth.basic
|
||||
import io.ktor.auth.BasicAuthenticationProvider
|
||||
import io.ktor.auth.jwt.jwt
|
||||
import io.ktor.auth.jwt.JWTAuthenticationProvider
|
||||
import org.leafygreens.kompendium.Kompendium
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaSecurity
|
||||
|
||||
object KompendiumAuth {
|
||||
|
||||
init {
|
||||
Kompendium.pathCalculator = AuthPathCalculator()
|
||||
}
|
||||
|
||||
fun Authentication.Configuration.notarizedBasic(
|
||||
name: String? = null,
|
||||
configure: BasicAuthenticationProvider.Configuration.() -> Unit
|
||||
) {
|
||||
Kompendium.openApiSpec.components.securitySchemes[name ?: "default"] = OpenApiSpecSchemaSecurity(
|
||||
type = "http",
|
||||
scheme = "basic"
|
||||
)
|
||||
basic(name, configure)
|
||||
}
|
||||
|
||||
fun Authentication.Configuration.notarizedJwt(
|
||||
name: String? = null,
|
||||
header: String? = null,
|
||||
scheme: String? = null,
|
||||
configure: JWTAuthenticationProvider.Configuration.() -> Unit
|
||||
) {
|
||||
if (header == null || header == "Authorization") {
|
||||
Kompendium.openApiSpec.components.securitySchemes[name ?: "default"] = OpenApiSpecSchemaSecurity(
|
||||
type = "http",
|
||||
scheme = scheme ?: "bearer"
|
||||
)
|
||||
} else {
|
||||
Kompendium.openApiSpec.components.securitySchemes[name ?: "default"] = OpenApiSpecSchemaSecurity(
|
||||
type = "apiKey",
|
||||
name = header,
|
||||
`in` = "header"
|
||||
)
|
||||
}
|
||||
jwt(name, configure)
|
||||
}
|
||||
|
||||
// TODO support other authentication providers (e.g., oAuth)?
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
package org.leafygreens.kompendium.auth
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.jackson.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.testing.*
|
||||
import org.junit.Test
|
||||
import org.leafygreens.kompendium.Kompendium
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedJwt
|
||||
import org.leafygreens.kompendium.auth.util.TestData
|
||||
import org.leafygreens.kompendium.auth.util.TestParams
|
||||
import org.leafygreens.kompendium.auth.util.TestResponse
|
||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.routes.openApi
|
||||
import org.leafygreens.kompendium.routes.redoc
|
||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class KompendiumAuthTest {
|
||||
|
||||
@AfterTest
|
||||
fun `reset kompendium`() {
|
||||
Kompendium.openApiSpec = OpenApiSpec(
|
||||
info = OpenApiSpecInfo(),
|
||||
servers = mutableListOf(),
|
||||
paths = mutableMapOf()
|
||||
)
|
||||
Kompendium.cache = emptyMap()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with basic authentication records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configBasicAuth()
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.Basic)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_basic_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with jwt authentication records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configJwtAuth()
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.JWT)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_jwt_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with jwt authentication and custom scheme records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configJwtAuth(scheme = "oauth")
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.JWT)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_jwt_custom_scheme_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with jwt authentication and custom header records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configJwtAuth(header = "x-api-key")
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.JWT)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_jwt_custom_header_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with multiple jwt schemes records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
install(Authentication) {
|
||||
notarizedJwt("jwt1", header = "x-api-key-1") {
|
||||
realm = "Ktor server"
|
||||
}
|
||||
notarizedJwt("jwt2", header = "x-api-key-2") {
|
||||
realm = "Ktor server"
|
||||
}
|
||||
}
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule("jwt1", "jwt2")
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_multiple_jwt_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.configModule() {
|
||||
install(ContentNegotiation) {
|
||||
jackson(ContentType.Application.Json) {
|
||||
enable(SerializationFeature.INDENT_OUTPUT)
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.configBasicAuth() {
|
||||
install(Authentication) {
|
||||
notarizedBasic(TestData.AuthConfigName.Basic) {
|
||||
realm = "Ktor Server"
|
||||
validate { credentials ->
|
||||
if (credentials.name == credentials.password) {
|
||||
UserIdPrincipal(credentials.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.configJwtAuth(
|
||||
header: String? = null,
|
||||
scheme: String? = null
|
||||
) {
|
||||
install(Authentication) {
|
||||
notarizedJwt(TestData.AuthConfigName.JWT, header, scheme) {
|
||||
realm = "Ktor server"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.notarizedAuthenticatedGetModule(vararg authenticationConfigName: String) {
|
||||
routing {
|
||||
authenticate(*authenticationConfigName) {
|
||||
route(TestData.getRoutePath) {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo(*authenticationConfigName)) {
|
||||
call.respondText { "hey dude ‼️ congratz on the get request" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val oas = Kompendium.openApiSpec.copy()
|
||||
|
||||
private fun Application.docs() {
|
||||
routing {
|
||||
openApi(oas)
|
||||
redoc(oas)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor")
|
||||
fun testGetInfo(vararg security: String) =
|
||||
MethodInfo("Another get test", "testing more", testGetResponse, securitySchemes = security.toSet())
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.leafygreens.kompendium.auth.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
object TestData {
|
||||
object AuthConfigName {
|
||||
val Basic = "basic"
|
||||
val JWT = "jwt"
|
||||
}
|
||||
|
||||
val getRoutePath = "/test"
|
||||
|
||||
fun getFileSnapshot(fileName: String): String {
|
||||
val snapshotPath = "src/test/resources"
|
||||
val file = File("$snapshotPath/$fileName")
|
||||
return file.readText()
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.leafygreens.kompendium.auth.util
|
||||
|
||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||
import org.leafygreens.kompendium.annotations.PathParam
|
||||
import org.leafygreens.kompendium.annotations.QueryParam
|
||||
|
||||
data class TestParams(
|
||||
@PathParam val a: String,
|
||||
@QueryParam val aa: Int
|
||||
)
|
||||
|
||||
data class TestRequest(
|
||||
@KompendiumField(name = "field_name")
|
||||
val b: Double,
|
||||
val aaa: List<Long>
|
||||
)
|
||||
|
||||
data class TestResponse(val c: String)
|
||||
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"basic" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"basic" : {
|
||||
"type" : "http",
|
||||
"scheme" : "basic"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt" : {
|
||||
"type" : "http",
|
||||
"scheme" : "bearer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt" : {
|
||||
"type" : "apiKey",
|
||||
"name" : "x-api-key",
|
||||
"in" : "header"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt" : {
|
||||
"type" : "http",
|
||||
"scheme" : "oauth"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt1" : [ ],
|
||||
"jwt2" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt1" : {
|
||||
"type" : "apiKey",
|
||||
"name" : "x-api-key-1",
|
||||
"in" : "header"
|
||||
},
|
||||
"jwt2" : {
|
||||
"type" : "apiKey",
|
||||
"name" : "x-api-key-2",
|
||||
"in" : "header"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -101,7 +101,11 @@ object Kompendium {
|
||||
deprecated = this.deprecated,
|
||||
parameters = paramType.toParameterSpec(),
|
||||
responses = responseType.toResponseSpec(responseInfo)?.let { mapOf(it) },
|
||||
requestBody = if (method != HttpMethod.Get) requestType.toRequestSpec(requestInfo) else null
|
||||
requestBody = if (method != HttpMethod.Get) requestType.toRequestSpec(requestInfo) else null,
|
||||
security = if (this.securitySchemes.isNotEmpty()) listOf(
|
||||
// TODO support scopes
|
||||
this.securitySchemes.associateWith { listOf() }
|
||||
) else null
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
|
@ -7,5 +7,6 @@ data class MethodInfo(
|
||||
val responseInfo: ResponseInfo? = null,
|
||||
val requestInfo: RequestInfo? = null,
|
||||
val tags: Set<String> = emptySet(),
|
||||
val deprecated: Boolean = false
|
||||
val deprecated: Boolean = false,
|
||||
val securitySchemes: Set<String> = emptySet()
|
||||
)
|
||||
|
@ -3,5 +3,5 @@ package org.leafygreens.kompendium.models.oas
|
||||
// TODO I *think* the only thing I need here is the security https://swagger.io/specification/#components-object
|
||||
data class OpenApiSpecComponents(
|
||||
val schemas: MutableMap<String, OpenApiSpecComponentSchema> = mutableMapOf(),
|
||||
val securitySchemes: MutableMap<String, OpenApiSpecSchema> = mutableMapOf()
|
||||
val securitySchemes: MutableMap<String, OpenApiSpecSchemaSecurity> = mutableMapOf()
|
||||
)
|
||||
|
@ -7,8 +7,10 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation(projects.kompendiumCore)
|
||||
implementation(projects.kompendiumAuth)
|
||||
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.ktorAuth)
|
||||
implementation(libs.bundles.logging)
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
|
@ -5,6 +5,9 @@ import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.auth.UserIdPrincipal
|
||||
import io.ktor.features.ContentNegotiation
|
||||
import io.ktor.jackson.jackson
|
||||
import io.ktor.response.respondText
|
||||
@ -18,6 +21,7 @@ import org.leafygreens.kompendium.Kompendium.notarizedDelete
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedPost
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedPut
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
|
||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||
import org.leafygreens.kompendium.annotations.PathParam
|
||||
import org.leafygreens.kompendium.annotations.QueryParam
|
||||
@ -28,6 +32,7 @@ import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testAuthenticatedSingleGetInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleDeleteInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
|
||||
@ -73,13 +78,30 @@ fun main() {
|
||||
).start(wait = true)
|
||||
}
|
||||
|
||||
var featuresInstalled = false
|
||||
fun Application.mainModule() {
|
||||
// only install once in case of auto reload
|
||||
if (!featuresInstalled) {
|
||||
install(ContentNegotiation) {
|
||||
jackson {
|
||||
enable(SerializationFeature.INDENT_OUTPUT)
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
}
|
||||
install(Authentication) {
|
||||
notarizedBasic("basic") {
|
||||
realm = "Ktor Server"
|
||||
validate { credentials ->
|
||||
if (credentials.name == credentials.password) {
|
||||
UserIdPrincipal(credentials.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
featuresInstalled = true
|
||||
}
|
||||
routing {
|
||||
openApi(oas)
|
||||
redoc(oas)
|
||||
@ -103,6 +125,13 @@ fun Application.mainModule() {
|
||||
call.respondText { "heya" }
|
||||
}
|
||||
}
|
||||
authenticate("basic") {
|
||||
route("/authenticated/single") {
|
||||
notarizedGet<Unit, Unit>(testAuthenticatedSingleGetInfo) {
|
||||
call.respondText("get authentiticated single")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,4 +209,14 @@ object KompendiumTOC {
|
||||
mediaTypes = emptyList()
|
||||
)
|
||||
)
|
||||
val testAuthenticatedSingleGetInfo = MethodInfo(
|
||||
summary = "Another get test",
|
||||
description = "testing more",
|
||||
tags = setOf("anotherTest", "sample"),
|
||||
responseInfo = ResponseInfo(
|
||||
status = KompendiumHttpCodes.OK,
|
||||
description = "Returns a different sample"
|
||||
),
|
||||
securitySchemes = setOf("basic")
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
rootProject.name = "kompendium"
|
||||
include("kompendium-core")
|
||||
include("kompendium-auth")
|
||||
include("kompendium-playground")
|
||||
|
||||
// Feature Previews
|
||||
|
Reference in New Issue
Block a user