From c1de507051bd55c1fbcdc568a8314da3a2495277 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Wed, 2 Oct 2024 02:56:52 +0200 Subject: [PATCH] feat: add a mocked auth service that is always returning a logged in user for development --- ....kt => DefaultKtorAuthnzAuthentication.kt} | 36 ++-------- ...ExampleKtorAuthnzAuthenticationProvider.kt | 34 +++++++++ .../auth/KtorAuthnzAuthenticationHelpers.kt | 28 ++++++++ .../auth/KtorAuthnzAuthenticationProvider.kt | 9 +++ .../kotlin/id/walt/KtorAuthnzDevMockedTest.kt | 71 +++++++++++++++++++ 5 files changed, 148 insertions(+), 30 deletions(-) rename waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/{KtorAuthnzAuthentication.kt => DefaultKtorAuthnzAuthentication.kt} (62%) create mode 100644 waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/ExampleKtorAuthnzAuthenticationProvider.kt create mode 100644 waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationHelpers.kt create mode 100644 waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationProvider.kt create mode 100644 waltid-libraries/auth/waltid-ktor-authnz/src/test/kotlin/id/walt/KtorAuthnzDevMockedTest.kt diff --git a/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthentication.kt b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/DefaultKtorAuthnzAuthentication.kt similarity index 62% rename from waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthentication.kt rename to waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/DefaultKtorAuthnzAuthentication.kt index 972f1ff53..3106b8c39 100644 --- a/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthentication.kt +++ b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/DefaultKtorAuthnzAuthentication.kt @@ -1,12 +1,9 @@ package id.walt.ktorauthnz.auth import id.walt.ktorauthnz.KtorAuthnzManager -import id.walt.ktorauthnz.sessions.AuthSession import io.ktor.http.* -import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.response.* -import io.ktor.util.pipeline.* /** * A `basic` [Authentication] provider. @@ -14,9 +11,9 @@ import io.ktor.util.pipeline.* * @see [basic] * @property name is the name of the provider, or `null` for a default provider. */ -public class KtorAuthnzAuthenticationProvider internal constructor( +class DefaultKtorAuthnzAuthentication internal constructor( config: Config, -) : AuthenticationProvider(config) { +) : KtorAuthnzAuthenticationProvider(config) { // private val challengeFunction: FormAuthChallengeFunction = config.challengeFunction @@ -61,7 +58,7 @@ public class KtorAuthnzAuthenticationProvider internal constructor( /** * A configuration for the ktor-authnz authentication provider. */ - public class Config internal constructor(name: String?) : AuthenticationProvider.Config(name) { + class Config internal constructor(name: String?) : AuthenticationProvider.Config(name) { /*internal var challengeFunction: FormAuthChallengeFunction = { call.respond(UnauthorizedResponse()) }*/ @@ -71,31 +68,10 @@ public class KtorAuthnzAuthenticationProvider internal constructor( /** * Installs the ktor-authnz [Authentication] provider. */ -public fun AuthenticationConfig.ktorAuthnz( +fun AuthenticationConfig.ktorAuthnz( name: String? = null, - configure: KtorAuthnzAuthenticationProvider.Config.() -> Unit, + configure: DefaultKtorAuthnzAuthentication.Config.() -> Unit, ) { - val provider = KtorAuthnzAuthenticationProvider(KtorAuthnzAuthenticationProvider.Config(name).apply(configure)) + val provider = DefaultKtorAuthnzAuthentication(DefaultKtorAuthnzAuthentication.Config(name).apply(configure)) register(provider) } - -fun PipelineContext.getAuthToken(): String { - val token = call.principal()?.name - check(token != null) { "No token for request principal" } - - return token -} - -// TODO: switch to @OptIn instead of @Deprecated -@Deprecated("Externally provided JWT token cannot resolve to authenticated session") -suspend fun PipelineContext.getAuthenticatedSession(): AuthSession { - val token = getAuthToken() - - return KtorAuthnzManager.tokenHandler.resolveTokenToSession(token) -} - -suspend fun PipelineContext.getAuthenticatedAccount(): String { - val token = getAuthToken() - - return KtorAuthnzManager.tokenHandler.getTokenAccountId(token) -} diff --git a/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/ExampleKtorAuthnzAuthenticationProvider.kt b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/ExampleKtorAuthnzAuthenticationProvider.kt new file mode 100644 index 000000000..4ffaf8a68 --- /dev/null +++ b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/ExampleKtorAuthnzAuthenticationProvider.kt @@ -0,0 +1,34 @@ +package id.walt.ktorauthnz.auth + +import id.walt.ktorauthnz.auth.ExampleKtorAuthnzAuthenticationProvider.ExampleKtorAuthnzConfig +import io.ktor.server.auth.* + +/** + * Mock authentication for development purposes, all requests will appear + * as logged in with a pre-defined token. + */ +class ExampleKtorAuthnzAuthenticationProvider(val config: ExampleKtorAuthnzConfig) : + KtorAuthnzAuthenticationProvider(config) { + + override suspend fun onAuthenticate(context: AuthenticationContext) { + context.principal(name, UserIdPrincipal(config.token)) + } + + /** + * Config for (development purpose) mocked authentication provider + * @param token token to always use + */ + class ExampleKtorAuthnzConfig(name: String? = null, val token: String) : Config(name) +} + +/** + * Installs a mocked ktor-authnz [Authentication] provider. + */ +fun AuthenticationConfig.devKtorAuthnzMocked( + name: String?, + token: String, + configure: ExampleKtorAuthnzConfig.() -> Unit, +) { + val provider = ExampleKtorAuthnzAuthenticationProvider(ExampleKtorAuthnzConfig(name, token).apply(configure)) + register(provider) +} diff --git a/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationHelpers.kt b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationHelpers.kt new file mode 100644 index 000000000..acbac43d3 --- /dev/null +++ b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationHelpers.kt @@ -0,0 +1,28 @@ +package id.walt.ktorauthnz.auth + +import id.walt.ktorauthnz.KtorAuthnzManager +import id.walt.ktorauthnz.sessions.AuthSession +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.util.pipeline.* + +fun PipelineContext.getAuthToken(): String { + val token = call.principal()?.name + check(token != null) { "No token for request principal" } + + return token +} + +// TODO: switch to @OptIn instead of @Deprecated +@Deprecated("Externally provided JWT token cannot resolve to authenticated session") +suspend fun PipelineContext.getAuthenticatedSession(): AuthSession { + val token = getAuthToken() + + return KtorAuthnzManager.tokenHandler.resolveTokenToSession(token) +} + +suspend fun PipelineContext.getAuthenticatedAccount(): String { + val token = getAuthToken() + + return KtorAuthnzManager.tokenHandler.getTokenAccountId(token) +} diff --git a/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationProvider.kt b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationProvider.kt new file mode 100644 index 000000000..8475d1f8d --- /dev/null +++ b/waltid-libraries/auth/waltid-ktor-authnz/src/main/kotlin/id/walt/ktorauthnz/auth/KtorAuthnzAuthenticationProvider.kt @@ -0,0 +1,9 @@ +package id.walt.ktorauthnz.auth + +import io.ktor.server.auth.* + +abstract class KtorAuthnzAuthenticationProvider(config: Config) : AuthenticationProvider(config) { + + abstract override suspend fun onAuthenticate(context: AuthenticationContext) + +} diff --git a/waltid-libraries/auth/waltid-ktor-authnz/src/test/kotlin/id/walt/KtorAuthnzDevMockedTest.kt b/waltid-libraries/auth/waltid-ktor-authnz/src/test/kotlin/id/walt/KtorAuthnzDevMockedTest.kt new file mode 100644 index 000000000..298240625 --- /dev/null +++ b/waltid-libraries/auth/waltid-ktor-authnz/src/test/kotlin/id/walt/KtorAuthnzDevMockedTest.kt @@ -0,0 +1,71 @@ +package id.walt + +import id.walt.ktorauthnz.KtorAuthnzManager +import id.walt.ktorauthnz.auth.devKtorAuthnzMocked +import id.walt.ktorauthnz.auth.getAuthenticatedAccount +import id.walt.ktorauthnz.auth.ktorAuthnz +import id.walt.ktorauthnz.sessions.AuthSession +import id.walt.ktorauthnz.sessions.AuthSessionStatus +import id.walt.ktorauthnz.tokens.ktorauthnztoken.KtorAuthNzTokenHandler +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.auth.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.testing.* +import kotlin.test.Test + +class KtorAuthnzDevMockedTest { + + @Test + fun testUnMockedAuth() = testApplication { + install(Authentication) { + ktorAuthnz { } + } + + routing { + authenticate { + get("/protected") { + context.respond("protected") + } + } + } + + val resp = client.get("/protected") + println(resp) + + check(!resp.status.isSuccess()) + } + + @Test + fun testMockedAuth() = testApplication { + install(Authentication) { + devKtorAuthnzMocked("dev-auth", "dev-token") { + } + } + + KtorAuthnzManager.sessionStore.wip_sessions["dev-session"] = AuthSession( + id = "dev-session", + status = AuthSessionStatus.OK, + token = "dev-token", + accountId = "11111111-1111-1111-1111-000000000000" + ) + (KtorAuthnzManager.tokenHandler as KtorAuthNzTokenHandler).tokenStore.tokens["dev-token"] = "dev-session" + + routing { + authenticate("dev-auth") { + get("/protected") { + val acc = getAuthenticatedAccount() + context.respond("protected! you are: $acc") + } + } + } + + val resp = client.get("/protected") + println(resp) + + check(resp.status.isSuccess()) + check("11111111-1111-1111-1111-000000000000" in resp.bodyAsText()) + } +}