[Kotlin Server Ktor] upgrade to stable version (1.1.3) (#2333)

* [Kotlin Server Ktor] upgrade to stable version (1.1.3)

* [Kotlin Server Ktor] upgrade to stable version (1.1.3)

fix indentation

* [Kotlin Server Ktor] upgrade to stable version (1.1.3)

fix indentation

* add missing kotlin ktor in Server stubs list

* [Kotlin Server Ktor] upgrade to stable version (1.1.3)

fix compilation warnings
This commit is contained in:
Vincent Devos
2019-03-14 09:38:39 +01:00
committed by William Cheng
parent 3e085e9492
commit f2ff473155
17 changed files with 247 additions and 378 deletions

View File

@@ -39,7 +39,7 @@ OpenAPI Generator allows generation of API client libraries (SDK generation), se
| | Languages/Frameworks |
|-|-|
**API clients** | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later), **C++** (cpprest, Qt5, Tizen), **Clojure**, **Dart (1.x, 2.x)**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client), **Kotlin**, **Lua**, **Node.js** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types) **Objective-C**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x), **Typescript** (AngularJS, Angular (2.x - 7.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs)
**Server stubs** | **Ada**, **C#** (ASP.NET Core, NancyFx), **C++** (Pistache, Restbed), **Erlang**, **Go** (net/http, Gin), **Haskell** (Servant), **Java** (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, RestEasy, Play Framework, [PKMST](https://github.com/ProKarma-Inc/pkmst-getting-started-examples)), **Kotlin** (Spring Boot), **PHP** (Laravel, Lumen, Slim, Silex, [Symfony](https://symfony.com/), [Zend Expressive](https://github.com/zendframework/zend-expressive)), **Python** (Flask), **NodeJS**, **Ruby** (Sinatra, Rails5), **Rust** (rust-server), **Scala** ([Finch](https://github.com/finagle/finch), [Lagom](https://github.com/lagom/lagom), Scalatra)
**Server stubs** | **Ada**, **C#** (ASP.NET Core, NancyFx), **C++** (Pistache, Restbed), **Erlang**, **Go** (net/http, Gin), **Haskell** (Servant), **Java** (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, RestEasy, Play Framework, [PKMST](https://github.com/ProKarma-Inc/pkmst-getting-started-examples)), **Kotlin** (Spring Boot, Ktor), **PHP** (Laravel, Lumen, Slim, Silex, [Symfony](https://symfony.com/), [Zend Expressive](https://github.com/zendframework/zend-expressive)), **Python** (Flask), **NodeJS**, **Ruby** (Sinatra, Rails5), **Rust** (rust-server), **Scala** ([Finch](https://github.com/finagle/finch), [Lagom](https://github.com/lagom/lagom), Scalatra)
**API documentation generators** | **HTML**, **Confluence Wiki**
**Configuration files** | [**Apache2**](https://httpd.apache.org/)
**Others** | **GraphQL**, **JMeter**, **MySQL Schema**

View File

@@ -3,38 +3,48 @@ package {{packageName}}.infrastructure
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.*
import io.ktor.http.auth.*
import io.ktor.request.ApplicationRequest
import io.ktor.response.respond
import io.ktor.application.*
import io.ktor.pipeline.*
import io.ktor.request.*
import io.ktor.response.*
import java.util.*
enum class ApiKeyLocation(val location: String) {
QUERY("query"),
HEADER("header")
}
data class ApiKey(val value: String): Credential
data class ApiPrincipal(val apiKey: ApiKey?) : Principal
fun ApplicationCall.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? = request.apiKey(key, keyLocation)
fun ApplicationRequest.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? {
val value: String? = when(keyLocation) {
ApiKeyLocation.QUERY -> this.queryParameters[key]
ApiKeyLocation.HEADER -> this.headers[key]
}
when (value) {
null -> return null
else -> return ApiKey(value)
data class ApiKeyCredential(val value: String): Credential
data class ApiPrincipal(val apiKeyCredential: ApiKeyCredential?) : Principal
/**
* Represents a Api Key authentication provider
* @param name is the name of the provider, or `null` for a default provider
*/
class ApiKeyAuthenticationProvider(name: String?) : AuthenticationProvider(name) {
internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = { null }
var apiKeyName: String = "";
var apiKeyLocation: ApiKeyLocation = ApiKeyLocation.QUERY;
/**
* Sets a validation function that will check given [ApiKeyCredential] instance and return [Principal],
* or null if credential does not correspond to an authenticated principal
*/
fun validate(body: suspend ApplicationCall.(ApiKeyCredential) -> Principal?) {
authenticationFunction = body
}
}
fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String, validate: suspend (ApiKey) -> ApiPrincipal?) {
intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKey(apiKeyName, ApiKeyLocation.values().first { it.location == authLocation })
val principal = credentials?.let { validate(it) }
fun Authentication.Configuration.apiKeyAuth(name: String? = null, configure: ApiKeyAuthenticationProvider.() -> Unit) {
val provider = ApiKeyAuthenticationProvider(name).apply(configure)
val apiKeyName = provider.apiKeyName
val apiKeyLocation = provider.apiKeyLocation
val authenticate = provider.authenticationFunction
provider.pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticate(call, it) }
val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
@@ -49,9 +59,20 @@ fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String,
it.complete()
}
}
if (principal != null) {
context.principal(principal)
}
}
}
fun ApplicationRequest.apiKeyAuthenticationCredentials(apiKeyName: String, apiKeyLocation: ApiKeyLocation): ApiKeyCredential? {
val value: String? = when(apiKeyLocation) {
ApiKeyLocation.QUERY -> this.queryParameters[apiKeyName]
ApiKeyLocation.HEADER -> this.headers[apiKeyName]
}
when (value) {
null -> return null
else -> return ApiKeyCredential(value)
}
}

View File

@@ -13,6 +13,11 @@ import io.ktor.locations.*
import io.ktor.metrics.*
import io.ktor.routing.*
import java.util.concurrent.*
{{#hasAuthMethods}}
import io.ktor.auth.*
import io.ktor.util.KtorExperimentalAPI
import org.openapitools.server.infrastructure.*
{{/hasAuthMethods}}
{{#generateApis}}
import {{apiPackage}}.*
{{/generateApis}}
@@ -20,12 +25,15 @@ import {{apiPackage}}.*
{{#imports}}import {{import}}
{{/imports}}
@KtorExperimentalAPI
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
object HTTP {
val client = HttpClient(Apache)
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Application.main() {
install(DefaultHeaders)
install(Metrics) {
@@ -56,6 +64,49 @@ fun Application.main() {
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
{{/featureCompression}}
install(Locations) // see http://ktor.io/features/locations.html
{{#hasAuthMethods}}
install(Authentication) {
{{#authMethods}}
{{#isBasic}}
basic("{{{name}}}") {
validate { credentials ->
// TODO: "Apply your basic authentication functionality."
// Accessible in-method via call.principal<UserIdPrincipal>()
if (credentials.name == "Swagger" && "Codegen" == credentials.password) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
{{/isBasic}}
{{#isApiKey}}
// "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'."
apiKeyAuth("{{{name}}}") {
validate { apikeyCredential: ApiKeyCredential ->
when {
apikeyCredential.value == "keyboardcat" -> ApiPrincipal(apikeyCredential)
else -> null
}
}
}
{{/isApiKey}}
{{#isOAuth}}
{{#bodyAllowed}}
{{/bodyAllowed}}
{{^bodyAllowed}}
oauth("{{name}}") {
client = HttpClient(Apache)
providerLookup = { ApplicationAuthProviders["{{{name}}}"] }
urlProvider = { _ ->
// TODO: define a callback url here.
"/"
}
}
{{/bodyAllowed}}
{{/isOAuth}}
{{/authMethods}}
}
{{/hasAuthMethods}}
install(Routing) {
{{#apiInfo}}
{{#apis}}
@@ -65,10 +116,11 @@ fun Application.main() {
{{/apis}}
{{/apiInfo}}
}
{{/generateApis}}
environment.monitor.subscribe(ApplicationStopping)
{
HTTP.client.close()
}
}
}

View File

@@ -4,6 +4,7 @@ package {{packageName}}
import io.ktor.auth.OAuthServerSettings
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.util.KtorExperimentalAPI
import java.time.Duration
import java.util.concurrent.Executors
@@ -77,6 +78,7 @@ internal fun ApplicationCompressionConfiguration(): Compression.Configuration.()
{{/featureCompression}}
// Defines authentication mechanisms used throughout the application.
@KtorExperimentalAPI
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
{{#authMethods}}
{{#isOAuth}}

View File

@@ -1,27 +1,13 @@
{{>licenseInfo}}
package {{packageName}}
import io.ktor.application.ApplicationCall
import io.ktor.http.HttpMethod
import io.ktor.locations.*
import io.ktor.pipeline.PipelineContext
import io.ktor.routing.Route
import io.ktor.routing.method
import {{modelPackage}}.*
{{#imports}}
import {{import}}
{{/imports}}
// NOTE: ktor-location@0.9.0 is missing extension for Route.delete. This includes it.
inline fun <reified T : Any> Route.delete(noinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit): Route {
return location(T::class) {
method(HttpMethod.Delete) {
handle(body)
}
}
}
{{#apiInfo}}
object Paths {
{{#apis}}
@@ -33,6 +19,7 @@ object Paths {
* {{#unescapedNotes}}{{.}}{{/unescapedNotes}}
{{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}*/
@KtorExperimentalLocationsAPI
@Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
{{/bodyAllowed}}

View File

@@ -8,8 +8,8 @@ Generated by OpenAPI Generator {{generatorVersion}}{{^hideGenerationTimestamp}}
## Requires
* Kotlin 1.2.10
* Gradle 4.3
* Kotlin 1.3.21
* Gradle 4.9
## Build

View File

@@ -5,8 +5,7 @@ import com.google.gson.Gson
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authentication
import io.ktor.auth.basicAuthentication
import io.ktor.auth.oauth
import io.ktor.auth.authenticate
import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
@@ -16,23 +15,15 @@ import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.*
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import {{packageName}}.ApplicationAuthProviders
import {{packageName}}.Paths
import {{packageName}}.ApplicationExecutors
import {{packageName}}.HTTP.client
import {{packageName}}.infrastructure.ApiPrincipal
import {{packageName}}.infrastructure.apiKeyAuth
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
// see https://github.com/ktorio/ktor/issues/288
import {{packageName}}.delete
{{#imports}}import {{import}}
{{/imports}}
{{#operations}}
@KtorExperimentalLocationsAPI
fun Route.{{classname}}() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
@@ -40,75 +31,27 @@ fun Route.{{classname}}() {
{{#bodyAllowed}}
route("{{path}}") {
{{#hasAuthMethods}}
{{#authMethods}}
authenticate("{{{name}}}") {
{{/authMethods}}
{{/hasAuthMethods}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} {
{{#lambda.indented_12}}{{>libraries/ktor/_api_body}}{{/lambda.indented_12}}
}
{{#hasAuthMethods}}
}
{{/hasAuthMethods}}
}
{{/bodyAllowed}}
{{^bodyAllowed}}
{{! NOTE: Locations can be used on routes without body parameters.}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { it: Paths.{{operationId}} ->
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { _: Paths.{{operationId}} ->
{{#lambda.indented_8}}{{>libraries/ktor/_api_body}}{{/lambda.indented_8}}
}
{{/bodyAllowed}}
{{! THis looks a little weird, but it's completely valid Kotlin code, and simplifies templated route logic above. }}
{{#hasAuthMethods}}.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
{{#authMethods}}
{{#isBasic}}
basicAuthentication("{{{name}}}") { credentials ->
// TODO: "Apply your basic authentication functionality."
// Accessible in-method via call.principal<UserIdPrincipal>()
if (credentials.name == "Swagger" && "Codegen" == credentials.password) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
{{/isBasic}}
{{#isApiKey}}
// "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'."
apiKeyAuth("{{{keyParamName}}}", {{#isKeyInQuery}}"query"{{/isKeyInQuery}}{{#isKeyInHeader}}"header"{{/isKeyInHeader}}) {
// TODO: "Verify key here , accessible as it.value"
if (it.value == "keyboardcat") {
ApiPrincipal(it)
} else {
null
}
}
{{/isApiKey}}
{{#isOAuth}}
{{#bodyAllowed}}
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["{{{name}}}"] }, {
// TODO: define a callback url here.
"/"
})
{{/bodyAllowed}}
{{^bodyAllowed}}
oauthAtLocation<Paths.{{operationId}}>(client, ApplicationExecutors.asCoroutineDispatcher(),
providerLookup = { ApplicationAuthProviders["{{{name}}}"] },
urlProvider = { currentLocation, provider ->
// TODO: define a callback url here.
"/"
})
{{/bodyAllowed}}
{{/isOAuth}}
{{/authMethods}}
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '{{path}}' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
{{/hasAuthMethods}}
{{^hasAuthMethods}}
{{/hasAuthMethods}}
{{/operation}}
}
{{/operations}}
{{/operations}}

View File

@@ -1,15 +1,15 @@
group '{{groupId}}'
version '{{artifactVersion}}'
task wrapper(type: Wrapper) {
gradleVersion = '4.3'
wrapper {
gradleVersion = '4.9'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
buildscript {
ext.kotlin_version = '1.2.10'
ext.ktor_version = '0.9.1-alpha-9'
ext.shadow_version = '2.0.2'
ext.kotlin_version = '1.3.21'
ext.ktor_version = '1.1.3'
ext.shadow_version = '2.0.3'
repositories {
mavenCentral()
@@ -43,12 +43,6 @@ compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
kotlin {
experimental {
coroutines "enable"
}
}
shadowJar {
baseName = '{{artifactId}}'
classifier = null
@@ -62,7 +56,7 @@ repositories {
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-metrics:$ktor_version"
compile "io.ktor:ktor-locations:$ktor_version"

View File

@@ -6,8 +6,8 @@ Generated by OpenAPI Generator 4.0.0-SNAPSHOT.
## Requires
* Kotlin 1.2.10
* Gradle 4.3
* Kotlin 1.3.21
* Gradle 4.9
## Build

View File

@@ -1,15 +1,15 @@
group 'org.openapitools'
version '1.0.0'
task wrapper(type: Wrapper) {
gradleVersion = '4.3'
wrapper {
gradleVersion = '4.9'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
buildscript {
ext.kotlin_version = '1.2.10'
ext.ktor_version = '0.9.1-alpha-9'
ext.shadow_version = '2.0.2'
ext.kotlin_version = '1.3.21'
ext.ktor_version = '1.1.3'
ext.shadow_version = '2.0.3'
repositories {
mavenCentral()
@@ -43,12 +43,6 @@ compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
kotlin {
experimental {
coroutines "enable"
}
}
shadowJar {
baseName = 'kotlin-server'
classifier = null
@@ -62,7 +56,7 @@ repositories {
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-metrics:$ktor_version"
compile "io.ktor:ktor-locations:$ktor_version"

View File

@@ -13,15 +13,21 @@ import io.ktor.locations.*
import io.ktor.metrics.*
import io.ktor.routing.*
import java.util.concurrent.*
import io.ktor.auth.*
import io.ktor.util.KtorExperimentalAPI
import org.openapitools.server.infrastructure.*
import org.openapitools.server.apis.*
@KtorExperimentalAPI
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
object HTTP {
val client = HttpClient(Apache)
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Application.main() {
install(DefaultHeaders)
install(Metrics) {
@@ -39,14 +45,34 @@ fun Application.main() {
install(HSTS, ApplicationHstsConfiguration()) // see http://ktor.io/features/hsts.html
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
install(Locations) // see http://ktor.io/features/locations.html
install(Authentication) {
// "Implement API key auth (api_key) for parameter name 'api_key'."
apiKeyAuth("api_key") {
validate { apikeyCredential: ApiKeyCredential ->
when {
apikeyCredential.value == "keyboardcat" -> ApiPrincipal(apikeyCredential)
else -> null
}
}
}
oauth("petstore_auth") {
client = HttpClient(Apache)
providerLookup = { ApplicationAuthProviders["petstore_auth"] }
urlProvider = { _ ->
// TODO: define a callback url here.
"/"
}
}
}
install(Routing) {
PetApi()
StoreApi()
UserApi()
}
environment.monitor.subscribe(ApplicationStopping)
{
HTTP.client.close()
}
}
}

View File

@@ -4,6 +4,7 @@ package org.openapitools.server
import io.ktor.auth.OAuthServerSettings
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.util.KtorExperimentalAPI
import java.time.Duration
import java.util.concurrent.Executors
@@ -50,6 +51,7 @@ internal fun ApplicationCompressionConfiguration(): Compression.Configuration.()
}
// Defines authentication mechanisms used throughout the application.
@KtorExperimentalAPI
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
OAuthServerSettings.OAuth2ServerSettings(
name = "petstore_auth",

View File

@@ -11,24 +11,10 @@
*/
package org.openapitools.server
import io.ktor.application.ApplicationCall
import io.ktor.http.HttpMethod
import io.ktor.locations.*
import io.ktor.pipeline.PipelineContext
import io.ktor.routing.Route
import io.ktor.routing.method
import org.openapitools.server.models.*
// NOTE: ktor-location@0.9.0 is missing extension for Route.delete. This includes it.
inline fun <reified T : Any> Route.delete(noinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit): Route {
return location(T::class) {
method(HttpMethod.Delete) {
handle(body)
}
}
}
object Paths {
/**
* Deletes a pet
@@ -36,6 +22,7 @@ object Paths {
* @param petId Pet id to delete
* @param apiKey (optional, default to null)
*/
@KtorExperimentalLocationsAPI
@Location("/pet/{petId}") class deletePet(val petId: kotlin.Long, val apiKey: kotlin.String)
/**
@@ -43,6 +30,7 @@ object Paths {
* Multiple status values can be provided with comma separated strings
* @param status Status values that need to be considered for filter
*/
@KtorExperimentalLocationsAPI
@Location("/pet/findByStatus") class findPetsByStatus(val status: kotlin.Array<kotlin.String>)
/**
@@ -50,6 +38,7 @@ object Paths {
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
* @param tags Tags to filter by
*/
@KtorExperimentalLocationsAPI
@Location("/pet/findByTags") class findPetsByTags(val tags: kotlin.Array<kotlin.String>)
/**
@@ -57,6 +46,7 @@ object Paths {
* Returns a single pet
* @param petId ID of pet to return
*/
@KtorExperimentalLocationsAPI
@Location("/pet/{petId}") class getPetById(val petId: kotlin.Long)
/**
@@ -64,12 +54,14 @@ object Paths {
* For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors
* @param orderId ID of the order that needs to be deleted
*/
@KtorExperimentalLocationsAPI
@Location("/store/order/{orderId}") class deleteOrder(val orderId: kotlin.String)
/**
* Returns pet inventories by status
* Returns a map of status codes to quantities
*/
@KtorExperimentalLocationsAPI
@Location("/store/inventory") class getInventory()
/**
@@ -77,6 +69,7 @@ object Paths {
* For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generated exceptions
* @param orderId ID of pet that needs to be fetched
*/
@KtorExperimentalLocationsAPI
@Location("/store/order/{orderId}") class getOrderById(val orderId: kotlin.Long)
/**
@@ -84,6 +77,7 @@ object Paths {
* This can only be done by the logged in user.
* @param username The name that needs to be deleted
*/
@KtorExperimentalLocationsAPI
@Location("/user/{username}") class deleteUser(val username: kotlin.String)
/**
@@ -91,6 +85,7 @@ object Paths {
*
* @param username The name that needs to be fetched. Use user1 for testing.
*/
@KtorExperimentalLocationsAPI
@Location("/user/{username}") class getUserByName(val username: kotlin.String)
/**
@@ -99,12 +94,14 @@ object Paths {
* @param username The user name for login
* @param password The password for login in clear text
*/
@KtorExperimentalLocationsAPI
@Location("/user/login") class loginUser(val username: kotlin.String, val password: kotlin.String)
/**
* Logs out current logged in user session
*
*/
@KtorExperimentalLocationsAPI
@Location("/user/logout") class logoutUser()
}

View File

@@ -15,8 +15,7 @@ import com.google.gson.Gson
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authentication
import io.ktor.auth.basicAuthentication
import io.ktor.auth.oauth
import io.ktor.auth.authenticate
import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
@@ -26,27 +25,20 @@ import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.*
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.openapitools.server.ApplicationAuthProviders
import org.openapitools.server.Paths
import org.openapitools.server.ApplicationExecutors
import org.openapitools.server.HTTP.client
import org.openapitools.server.infrastructure.ApiPrincipal
import org.openapitools.server.infrastructure.apiKeyAuth
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
// see https://github.com/ktorio/ktor/issues/288
import org.openapitools.server.delete
import org.openapitools.server.models.ApiResponse
import org.openapitools.server.models.Pet
@KtorExperimentalLocationsAPI
fun Route.PetApi() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
route("/pet") {
authenticate("petstore_auth") {
post {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
@@ -56,25 +48,11 @@ fun Route.PetApi() {
call.respond(HttpStatusCode.NotImplemented)
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
delete<Paths.deletePet> { it: Paths.deletePet ->
delete<Paths.deletePet> { _: Paths.deletePet ->
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal == null) {
@@ -83,26 +61,9 @@ fun Route.PetApi() {
call.respond(HttpStatusCode.NotImplemented)
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauthAtLocation<Paths.deletePet>(client, ApplicationExecutors.asCoroutineDispatcher(),
providerLookup = { ApplicationAuthProviders["petstore_auth"] },
urlProvider = { currentLocation, provider ->
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet/{petId}' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
get<Paths.findPetsByStatus> { it: Paths.findPetsByStatus ->
get<Paths.findPetsByStatus> { _: Paths.findPetsByStatus ->
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal == null) {
@@ -134,26 +95,9 @@ fun Route.PetApi() {
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauthAtLocation<Paths.findPetsByStatus>(client, ApplicationExecutors.asCoroutineDispatcher(),
providerLookup = { ApplicationAuthProviders["petstore_auth"] },
urlProvider = { currentLocation, provider ->
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet/findByStatus' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
get<Paths.findPetsByTags> { it: Paths.findPetsByTags ->
get<Paths.findPetsByTags> { _: Paths.findPetsByTags ->
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal == null) {
@@ -185,26 +129,9 @@ fun Route.PetApi() {
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauthAtLocation<Paths.findPetsByTags>(client, ApplicationExecutors.asCoroutineDispatcher(),
providerLookup = { ApplicationAuthProviders["petstore_auth"] },
urlProvider = { currentLocation, provider ->
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet/findByTags' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
get<Paths.getPetById> { it: Paths.getPetById ->
get<Paths.getPetById> { _: Paths.getPetById ->
val principal = call.authentication.principal<ApiPrincipal>()
if (principal == null) {
@@ -236,29 +163,10 @@ fun Route.PetApi() {
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
// "Implement API key auth (api_key) for parameter name 'api_key'."
apiKeyAuth("api_key", "header") {
// TODO: "Verify key here , accessible as it.value"
if (it.value == "keyboardcat") {
ApiPrincipal(it)
} else {
null
}
}
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet/{petId}' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
route("/pet") {
authenticate("petstore_auth") {
put {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
@@ -268,25 +176,12 @@ fun Route.PetApi() {
call.respond(HttpStatusCode.NotImplemented)
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
route("/pet/{petId}") {
authenticate("petstore_auth") {
post {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
@@ -296,25 +191,12 @@ fun Route.PetApi() {
call.respond(HttpStatusCode.NotImplemented)
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet/{petId}' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
route("/pet/{petId}/uploadImage") {
authenticate("petstore_auth") {
post {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
@@ -335,21 +217,7 @@ fun Route.PetApi() {
}
}
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
oauth(client, ApplicationExecutors.asCoroutineDispatcher(), { ApplicationAuthProviders["petstore_auth"] }, {
// TODO: define a callback url here.
"/"
})
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/pet/{petId}/uploadImage' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
}

View File

@@ -15,8 +15,7 @@ import com.google.gson.Gson
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authentication
import io.ktor.auth.basicAuthentication
import io.ktor.auth.oauth
import io.ktor.auth.authenticate
import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
@@ -26,31 +25,23 @@ import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.*
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.openapitools.server.ApplicationAuthProviders
import org.openapitools.server.Paths
import org.openapitools.server.ApplicationExecutors
import org.openapitools.server.HTTP.client
import org.openapitools.server.infrastructure.ApiPrincipal
import org.openapitools.server.infrastructure.apiKeyAuth
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
// see https://github.com/ktorio/ktor/issues/288
import org.openapitools.server.delete
import org.openapitools.server.models.Order
@KtorExperimentalLocationsAPI
fun Route.StoreApi() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
delete<Paths.deleteOrder> { it: Paths.deleteOrder ->
delete<Paths.deleteOrder> { _: Paths.deleteOrder ->
call.respond(HttpStatusCode.NotImplemented)
}
get<Paths.getInventory> { it: Paths.getInventory ->
get<Paths.getInventory> { _: Paths.getInventory ->
val principal = call.authentication.principal<ApiPrincipal>()
if (principal == null) {
@@ -59,29 +50,9 @@ fun Route.StoreApi() {
call.respond(HttpStatusCode.NotImplemented)
}
}
.apply {
// TODO: ktor doesn't allow different authentication registrations for endpoints sharing the same path but different methods.
// It could be the authentication block is being abused here. Until this is resolved, swallow duplicate exceptions.
try {
authentication {
// "Implement API key auth (api_key) for parameter name 'api_key'."
apiKeyAuth("api_key", "header") {
// TODO: "Verify key here , accessible as it.value"
if (it.value == "keyboardcat") {
ApiPrincipal(it)
} else {
null
}
}
}
} catch(e: io.ktor.application.DuplicateApplicationFeatureException){
application.environment.log.warn("authentication block for '/store/inventory' is duplicated in code. " +
"Generated endpoints may need to be merged under a 'route' entry.")
}
}
get<Paths.getOrderById> { it: Paths.getOrderById ->
get<Paths.getOrderById> { _: Paths.getOrderById ->
val exampleContentType = "application/json"
val exampleContentString = """{
"petId" : 6,
@@ -98,7 +69,7 @@ fun Route.StoreApi() {
else -> call.respondText(exampleContentString)
}
}
route("/store/order") {
post {
@@ -119,5 +90,5 @@ fun Route.StoreApi() {
}
}
}
}

View File

@@ -15,8 +15,7 @@ import com.google.gson.Gson
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authentication
import io.ktor.auth.basicAuthentication
import io.ktor.auth.oauth
import io.ktor.auth.authenticate
import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
@@ -26,21 +25,13 @@ import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.*
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import org.openapitools.server.ApplicationAuthProviders
import org.openapitools.server.Paths
import org.openapitools.server.ApplicationExecutors
import org.openapitools.server.HTTP.client
import org.openapitools.server.infrastructure.ApiPrincipal
import org.openapitools.server.infrastructure.apiKeyAuth
// ktor 0.9.x is missing io.ktor.locations.DELETE, this adds it.
// see https://github.com/ktorio/ktor/issues/288
import org.openapitools.server.delete
import org.openapitools.server.models.User
@KtorExperimentalLocationsAPI
fun Route.UserApi() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
@@ -50,28 +41,28 @@ fun Route.UserApi() {
call.respond(HttpStatusCode.NotImplemented)
}
}
route("/user/createWithArray") {
post {
call.respond(HttpStatusCode.NotImplemented)
}
}
route("/user/createWithList") {
post {
call.respond(HttpStatusCode.NotImplemented)
}
}
delete<Paths.deleteUser> { it: Paths.deleteUser ->
delete<Paths.deleteUser> { _: Paths.deleteUser ->
call.respond(HttpStatusCode.NotImplemented)
}
get<Paths.getUserByName> { it: Paths.getUserByName ->
get<Paths.getUserByName> { _: Paths.getUserByName ->
val exampleContentType = "application/json"
val exampleContentString = """{
"firstName" : "firstName",
@@ -90,22 +81,22 @@ fun Route.UserApi() {
else -> call.respondText(exampleContentString)
}
}
get<Paths.loginUser> { it: Paths.loginUser ->
get<Paths.loginUser> { _: Paths.loginUser ->
call.respond(HttpStatusCode.NotImplemented)
}
get<Paths.logoutUser> { it: Paths.logoutUser ->
get<Paths.logoutUser> { _: Paths.logoutUser ->
call.respond(HttpStatusCode.NotImplemented)
}
route("/user/{username}") {
put {
call.respond(HttpStatusCode.NotImplemented)
}
}
}

View File

@@ -3,38 +3,48 @@ package org.openapitools.server.infrastructure
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.*
import io.ktor.http.auth.*
import io.ktor.request.ApplicationRequest
import io.ktor.response.respond
import io.ktor.application.*
import io.ktor.pipeline.*
import io.ktor.request.*
import io.ktor.response.*
import java.util.*
enum class ApiKeyLocation(val location: String) {
QUERY("query"),
HEADER("header")
}
data class ApiKey(val value: String): Credential
data class ApiPrincipal(val apiKey: ApiKey?) : Principal
fun ApplicationCall.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? = request.apiKey(key, keyLocation)
fun ApplicationRequest.apiKey(key: String, keyLocation: ApiKeyLocation = ApiKeyLocation.valueOf("header")): ApiKey? {
val value: String? = when(keyLocation) {
ApiKeyLocation.QUERY -> this.queryParameters[key]
ApiKeyLocation.HEADER -> this.headers[key]
}
when (value) {
null -> return null
else -> return ApiKey(value)
data class ApiKeyCredential(val value: String): Credential
data class ApiPrincipal(val apiKeyCredential: ApiKeyCredential?) : Principal
/**
* Represents a Api Key authentication provider
* @param name is the name of the provider, or `null` for a default provider
*/
class ApiKeyAuthenticationProvider(name: String?) : AuthenticationProvider(name) {
internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = { null }
var apiKeyName: String = "";
var apiKeyLocation: ApiKeyLocation = ApiKeyLocation.QUERY;
/**
* Sets a validation function that will check given [ApiKeyCredential] instance and return [Principal],
* or null if credential does not correspond to an authenticated principal
*/
fun validate(body: suspend ApplicationCall.(ApiKeyCredential) -> Principal?) {
authenticationFunction = body
}
}
fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String, validate: suspend (ApiKey) -> ApiPrincipal?) {
intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKey(apiKeyName, ApiKeyLocation.values().first { it.location == authLocation })
val principal = credentials?.let { validate(it) }
fun Authentication.Configuration.apiKeyAuth(name: String? = null, configure: ApiKeyAuthenticationProvider.() -> Unit) {
val provider = ApiKeyAuthenticationProvider(name).apply(configure)
val apiKeyName = provider.apiKeyName
val apiKeyLocation = provider.apiKeyLocation
val authenticate = provider.authenticationFunction
provider.pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticate(call, it) }
val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
@@ -49,9 +59,20 @@ fun AuthenticationPipeline.apiKeyAuth(apiKeyName: String, authLocation: String,
it.complete()
}
}
if (principal != null) {
context.principal(principal)
}
}
}
fun ApplicationRequest.apiKeyAuthenticationCredentials(apiKeyName: String, apiKeyLocation: ApiKeyLocation): ApiKeyCredential? {
val value: String? = when(apiKeyLocation) {
ApiKeyLocation.QUERY -> this.queryParameters[apiKeyName]
ApiKeyLocation.HEADER -> this.headers[apiKeyName]
}
when (value) {
null -> return null
else -> return ApiKeyCredential(value)
}
}