Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
import io.ktor.server.application.BaseRouteScopedPlugin
import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.application.plugin
import io.ktor.server.auth.Authentication
import io.ktor.server.auth.AuthenticationContext
import io.ktor.server.auth.AuthenticationProvider
import io.ktor.server.auth.authenticate
import io.ktor.server.auth.jwt.jwt
import io.ktor.server.auth.principal
import io.ktor.server.html.respondHtml
import io.ktor.server.plugins.forwardedheaders.XForwardedHeaders
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.response.respond
Expand All @@ -31,14 +31,15 @@
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import io.ktor.util.AttributeKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.modelix.authorization.permissions.PermissionEvaluator
import org.modelix.authorization.permissions.PermissionInstanceReference
import org.modelix.authorization.permissions.PermissionParser
import org.modelix.authorization.permissions.PermissionParts
import org.modelix.authorization.permissions.SchemaInstance
import java.nio.charset.StandardCharsets
import java.util.Base64
import java.util.Collections
import org.modelix.authorization.permissions.recordKnownRoles
import org.modelix.authorization.permissions.recordKnownUser
import java.util.concurrent.TimeUnit

private val LOG = mu.KotlinLogging.logger { }
Expand Down Expand Up @@ -85,7 +86,18 @@
}
validate {
try {
jwtFromHeaders()?.let(::AccessTokenPrincipal)
val authPlugin = application.plugin(ModelixAuthorization)
val authConfig = authPlugin.config
jwtFromHeaders()
?.let { authConfig.nullIfInvalid(it) }

Check warning

Code scanning / detekt

Name shadowed: implicit lambda parameter 'it' Warning

Name shadowed: implicit lambda parameter 'it'
?.also { jwt ->
application.launch(Dispatchers.IO) {

Check warning

Code scanning / detekt

Dispatcher IO is used without dependency injection. Warning

Dispatcher IO is used without dependency injection.
val accessControlPersistence = authConfig.accessControlPersistence
accessControlPersistence.recordKnownUser(authConfig.jwtUtil.extractUserId(jwt))
accessControlPersistence.recordKnownRoles(authConfig.jwtUtil.extractUserRoles(jwt))
}
}
?.let(::AccessTokenPrincipal)
} catch (e: Exception) {
LOG.warn(e) { "Failed to read JWT token" }
null
Expand Down Expand Up @@ -146,11 +158,6 @@
)
}
}
get("permissions") {
call.respondHtml {
buildPermissionPage(call.getPermissionEvaluator())
}
}
}
if (config.permissionManagementEnabled) {
installPermissionManagementHandlers()
Expand All @@ -168,13 +175,10 @@

class ModelixAuthorizationPluginInstance(val config: ModelixAuthorizationConfig) {

private val deniedPermissionRequests: MutableSet<DeniedPermissionRequest> = Collections.synchronizedSet(LinkedHashSet())
private val permissionCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build<Pair<AccessTokenPrincipal, PermissionInstanceReference>, Boolean>()

fun getDeniedPermissions(): Set<DeniedPermissionRequest> = deniedPermissionRequests.toSet()

fun hasPermission(call: ApplicationCall, permissionToCheck: PermissionParts): Boolean {
return hasPermission(call, PermissionParser(config.permissionSchema).parse(permissionToCheck))
}
Expand All @@ -184,23 +188,7 @@

val principal = call.principal<AccessTokenPrincipal>() ?: throw NotLoggedInException()
return permissionCache.get(principal to permissionToCheck) {
getPermissionEvaluator(principal).hasPermission(permissionToCheck).also { granted ->
if (!granted) {
val userId = principal.getUserName()
if (userId != null) {
synchronized(deniedPermissionRequests) {
deniedPermissionRequests += DeniedPermissionRequest(
permissionRef = permissionToCheck,
userId = userId,
jwtPayload = principal.jwt.payload,
)
while (deniedPermissionRequests.size >= 100) {
deniedPermissionRequests.iterator().also { it.next() }.remove()
}
}
}
}
}
getPermissionEvaluator(principal).hasPermission(permissionToCheck)
}
}

Expand All @@ -227,14 +215,6 @@
}
}

data class DeniedPermissionRequest(
val permissionRef: PermissionInstanceReference,
val userId: String,
val jwtPayload: String,
) {
fun jwtPayloadJson() = String(Base64.getUrlDecoder().decode(jwtPayload), StandardCharsets.UTF_8)
}

/**
* Returns an [JWTVerifier] that wraps our common authorization logic,
* so that it can be configured in the verification with Ktor's JWT authorization.
Expand Down
Loading
Loading