Skip to content
Permalink
Browse files

[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
  • Loading branch information...
karismann authored and wing328 committed Mar 14, 2019
1 parent 3e085e9 commit f2ff473155b80087c078926c79bc792c32581e28
Showing with 247 additions and 378 deletions.
  1. +1 −1 README.md
  2. +43 −22 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/ApiKeyAuth.kt.mustache
  3. +53 −1 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/AppMain.kt.mustache
  4. +2 −0 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/Configuration.kt.mustache
  5. +1 −14 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/Paths.kt.mustache
  6. +2 −2 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/README.mustache
  7. +12 −69 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/api.mustache
  8. +6 −12 modules/openapi-generator/src/main/resources/kotlin-server/libraries/ktor/build.gradle.mustache
  9. +2 −2 samples/server/petstore/kotlin-server/ktor/README.md
  10. +6 −12 samples/server/petstore/kotlin-server/ktor/build.gradle
  11. +27 −1 samples/server/petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/AppMain.kt
  12. +2 −0 samples/server/petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/Configuration.kt
  13. +11 −14 samples/server/petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/Paths.kt
  14. +14 −146 samples/server/petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/apis/PetApi.kt
  15. +8 −37 samples/server/petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/apis/StoreApi.kt
  16. +14 −23 samples/server/petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/apis/UserApi.kt
  17. +43 −22 .../petstore/kotlin-server/ktor/src/main/kotlin/org/openapitools/server/infrastructure/ApiKeyAuth.kt
@@ -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**
@@ -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)
}
}
@@ -13,19 +13,27 @@ 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}}

{{#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()
}
}
}
@@ -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}}
@@ -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}}
@@ -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

@@ -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,99 +15,43 @@ 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?>()
{{#operation}}
{{#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}}
Oops, something went wrong.

0 comments on commit f2ff473

Please sign in to comment.
You can’t perform that action at this time.