Skip to content

Commit

Permalink
Replace homeserver authorization approach with an Authorization hea…
Browse files Browse the repository at this point in the history
…der instead of `access_token` when talking to the application service, as per [MSC2832](matrix-org/matrix-spec-proposals#2832).
  • Loading branch information
benkuly committed Nov 2, 2022
1 parent d02c263 commit e7a2450
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 56 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.folivo.trixnity.applicationserviceapi.server

import io.ktor.http.*
import io.ktor.http.auth.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import net.folivo.trixnity.core.ErrorResponse

class MatrixQueryParameterOrBearerAuthenticationProvider internal constructor(
configuration: Configuration,
private val field: String,
private val token: String
) : AuthenticationProvider(configuration) {
class Configuration internal constructor(name: String? = null) : Config(name)

override suspend fun onAuthenticate(context: AuthenticationContext) {
val queryCredentials = context.call.request.queryParameters[field]
val accessTokenCredentials = context.call.request.getAccessTokenFromHeader()
val cause = when {
queryCredentials == null && accessTokenCredentials == null -> AuthenticationFailedCause.NoCredentials
accessTokenCredentials != null && queryCredentials != null && accessTokenCredentials != queryCredentials
|| (accessTokenCredentials == null && queryCredentials != token)
|| (queryCredentials == null && accessTokenCredentials != token)
|| (accessTokenCredentials == queryCredentials && queryCredentials != token) -> AuthenticationFailedCause.InvalidCredentials

else -> null
}

if (cause != null) {
context.challenge("MatrixQueryParameterAuth", cause) { challenge, call ->
when (cause) {
AuthenticationFailedCause.NoCredentials ->
call.respond<ErrorResponse>(HttpStatusCode.Unauthorized, ErrorResponse.Unauthorized())

else -> call.respond<ErrorResponse>(HttpStatusCode.Forbidden, ErrorResponse.Forbidden())
}
challenge.complete()
}
} else {
context.principal(UserIdPrincipal("homeserver"))
}
}
}

private fun ApplicationRequest.getAccessTokenFromHeader(): String? {
return when (val authHeader = parseAuthorizationHeader()) {
is HttpAuthHeader.Single -> {
if (!authHeader.authScheme.equals("Bearer", ignoreCase = true)) null
else authHeader.blob
}

else -> null
}
}

fun AuthenticationConfig.matrixQueryParameterOrBearer(
name: String? = null,
field: String,
token: String,
) {
val provider =
MatrixQueryParameterOrBearerAuthenticationProvider(
MatrixQueryParameterOrBearerAuthenticationProvider.Configuration(name),
field,
token
)
register(provider)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fun Application.matrixApplicationServiceApiServer(
routes: Route.() -> Unit,
) {
install(Authentication) {
matrixQueryParameter("matrix-query-parameter-auth", "access_token", hsToken)
matrixQueryParameterOrBearer("matrix-query-parameter-auth", "access_token", hsToken)
}
matrixApiServer(json) {
authenticate("matrix-query-parameter-auth") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private fun Application.testAppMatrixQueryParameterAuth() {
json()
}
install(Authentication) {
matrixQueryParameter(null, "access_token", "validToken")
matrixQueryParameterOrBearer(null, "access_token", "validToken")
}
routing {
authenticate {
Expand All @@ -34,10 +34,10 @@ private fun Application.testAppMatrixQueryParameterAuth() {
}
}

class MatrixQueryParameterAuthTest {
class MatrixQueryParameterOrBearerAuthTest {

@Test
fun `should forbid missing token`() = testApplication {
fun `should forbid missing query or auth token`() = testApplication {
application { testAppMatrixQueryParameterAuth() }
val response = client.get("/_matrix/something")
assertEquals(HttpStatusCode.Unauthorized, response.status)
Expand All @@ -47,7 +47,7 @@ class MatrixQueryParameterAuthTest {
}

@Test
fun `should forbid wrong token`() = testApplication {
fun `should forbid wrong query token`() = testApplication {
application { testAppMatrixQueryParameterAuth() }
val response = client.get("/_matrix/something?access_token=invalidToken")
assertEquals(HttpStatusCode.Forbidden, response.status)
Expand All @@ -57,9 +57,42 @@ class MatrixQueryParameterAuthTest {
}

@Test
fun `should permit right token`() = testApplication {
fun `should forbid wrong header token`() = testApplication {
application { testAppMatrixQueryParameterAuth() }
val response = client.get("/_matrix/something") {
bearerAuth("invalidToken")
}
assertEquals(HttpStatusCode.Forbidden, response.status)
assertEquals(ContentType.Application.Json.withCharset(UTF_8), response.contentType())
Json.decodeFromString(ErrorResponseSerializer, response.body())
.shouldBeInstanceOf<ErrorResponse.Forbidden>()
}

@Test
fun `should forbid non matching token`() = testApplication {
application { testAppMatrixQueryParameterAuth() }
val response = client.get("/_matrix/something?access_token=validToken") {
bearerAuth("invalidToken")
}
assertEquals(HttpStatusCode.Forbidden, response.status)
assertEquals(ContentType.Application.Json.withCharset(UTF_8), response.contentType())
Json.decodeFromString(ErrorResponseSerializer, response.body())
.shouldBeInstanceOf<ErrorResponse.Forbidden>()
}

@Test
fun `should permit right query token`() = testApplication {
application { testAppMatrixQueryParameterAuth() }
val response = client.get("/_matrix/something?access_token=validToken")
assertEquals(HttpStatusCode.OK, response.status)
}

@Test
fun `should permit right header token`() = testApplication {
application { testAppMatrixQueryParameterAuth() }
val response = client.get("/_matrix/something") {
bearerAuth("validToken")
}
assertEquals(HttpStatusCode.OK, response.status)
}
}

0 comments on commit e7a2450

Please sign in to comment.