From 24a94ad646108c2421036eda429c973a826f5107 Mon Sep 17 00:00:00 2001 From: Lukas Forst Date: Fri, 30 Sep 2022 15:15:43 +0200 Subject: [PATCH] Update to latest Ktor (#7) --- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/Deps.kt | 4 ++-- .../dev/forst/ktor/csp/ContentSecurityPolicy.kt | 13 ++++++++++++- .../dev/forst/ktor/csp/TestMinimalExampleApp.kt | 12 +++++++++--- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 399f924..db7436d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ import io.gitlab.arturbosch.detekt.Detekt import org.gradle.jvm.tasks.Jar plugins { - kotlin("jvm") version "1.7.10" + kotlin("jvm") version "1.7.20" `maven-publish` signing diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt index 27992e7..0a31c45 100644 --- a/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -1,6 +1,6 @@ object Versions { const val detekt = "1.20.0" - const val ktor = "2.1.1" + const val ktor = "2.1.2" const val jupiterVersion = "5.9.0" } @@ -25,4 +25,4 @@ object Libs { const val jupiterParams = "org.junit.jupiter:junit-jupiter-params:${Versions.jupiterVersion}" const val jupiterRuntime = "org.junit.jupiter:junit-jupiter-engine:${Versions.jupiterVersion}" } -} \ No newline at end of file +} diff --git a/ktor-content-security-policy/src/main/kotlin/dev/forst/ktor/csp/ContentSecurityPolicy.kt b/ktor-content-security-policy/src/main/kotlin/dev/forst/ktor/csp/ContentSecurityPolicy.kt index 20d3828..f6ebb89 100644 --- a/ktor-content-security-policy/src/main/kotlin/dev/forst/ktor/csp/ContentSecurityPolicy.kt +++ b/ktor-content-security-policy/src/main/kotlin/dev/forst/ktor/csp/ContentSecurityPolicy.kt @@ -1,6 +1,8 @@ package dev.forst.ktor.csp +import io.ktor.http.HttpHeaders import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.PluginBuilder import io.ktor.server.application.createRouteScopedPlugin /** @@ -18,6 +20,9 @@ class ContentSecurityPolicyConfiguration { * Call specific policy, if it returns map, it is used as a csp policy. When returns null, * the CSP Header is not set. * + * The `call` and `body` are coming from the [PluginBuilder.onCallRespond], + * see more documentation there. + * * By default, it uses strict policy "default-src 'none'". */ var policy: (call: ApplicationCall, body: Any) -> Map? = { _, _ -> mapOf("default-src" to "'none'") } @@ -65,11 +70,17 @@ val ContentSecurityPolicy = createRouteScopedPlugin( onCallRespond { call, body -> if (!skip(call)) { val header = policy(call, body)?.toCspHeader() ?: return@onCallRespond - call.response.headers.append("Content-Security-Policy", header) + call.response.headers.append(HttpHeaders.ContentSecurityPolicy, header) } } } +/** + * CSP Header name as extension for [HttpHeaders]. + */ +val HttpHeaders.ContentSecurityPolicy: String + get() = "Content-Security-Policy" + private fun Map.toCspHeader(): String = this .map { (key, value) -> if (value != null) "$key $value" else key } .joinToString(";") diff --git a/ktor-content-security-policy/src/test/kotlin/dev/forst/ktor/csp/TestMinimalExampleApp.kt b/ktor-content-security-policy/src/test/kotlin/dev/forst/ktor/csp/TestMinimalExampleApp.kt index 4052050..0d4acb3 100644 --- a/ktor-content-security-policy/src/test/kotlin/dev/forst/ktor/csp/TestMinimalExampleApp.kt +++ b/ktor-content-security-policy/src/test/kotlin/dev/forst/ktor/csp/TestMinimalExampleApp.kt @@ -1,6 +1,7 @@ package dev.forst.ktor.csp import io.ktor.client.request.get +import io.ktor.http.HttpHeaders import io.ktor.server.application.Application import io.ktor.server.testing.testApplication import org.junit.jupiter.api.Test @@ -8,16 +9,21 @@ import kotlin.test.assertEquals import kotlin.test.assertNull class TestMinimalExampleApp { + @Test + fun `test csp header is correct`() { + assertEquals("Content-Security-Policy", HttpHeaders.ContentSecurityPolicy) + } + @Test fun `test minimal example app works as expected`() = testApplication { application(Application::minimalExample) // this should return csp header var responseWithCsp = client.get("/") - assertEquals("default-src 'self'", responseWithCsp.headers["Content-Security-Policy"]) + assertEquals("default-src 'self'", responseWithCsp.headers[HttpHeaders.ContentSecurityPolicy]) responseWithCsp = client.get("/specific") - assertEquals("default-src 'none'", responseWithCsp.headers["Content-Security-Policy"]) + assertEquals("default-src 'none'", responseWithCsp.headers[HttpHeaders.ContentSecurityPolicy]) // this should not val skippedResponse = client.get("/ignored") - assertNull(skippedResponse.headers["Content-Security-Policy"]) + assertNull(skippedResponse.headers[HttpHeaders.ContentSecurityPolicy]) } }