@@ -34,6 +34,9 @@ import io.ktor.util.AttributeKey
34
34
import org.modelix.authorization.permissions.PermissionEvaluator
35
35
import org.modelix.authorization.permissions.PermissionParts
36
36
import org.modelix.authorization.permissions.SchemaInstance
37
+ import java.nio.charset.StandardCharsets
38
+ import java.util.Base64
39
+ import java.util.Collections
37
40
import java.util.concurrent.TimeUnit
38
41
39
42
private val LOG = mu.KotlinLogging .logger { }
@@ -110,10 +113,10 @@ object ModelixAuthorization : BaseRouteScopedPlugin<IModelixAuthorizationConfig,
110
113
}
111
114
}
112
115
113
- if (config.debugEndpointsEnabled) {
114
- application.routing {
115
- authenticate( MODELIX_JWT_AUTH ) {
116
- (installedIntoRoute ? : this ). apply {
116
+ application.routing {
117
+ authenticate( MODELIX_JWT_AUTH ) {
118
+ (installedIntoRoute ? : this ). apply {
119
+ if (config.debugEndpointsEnabled) {
117
120
get(" /user" ) {
118
121
val jwt = call.principal<AccessTokenPrincipal >()?.jwt ? : call.jwtFromHeaders()
119
122
if (jwt == null ) {
@@ -144,9 +147,13 @@ object ModelixAuthorization : BaseRouteScopedPlugin<IModelixAuthorizationConfig,
144
147
}
145
148
}
146
149
}
150
+ if (config.permissionManagementEnabled) {
151
+ installPermissionManagementHandlers()
152
+ }
147
153
}
148
154
}
149
155
}
156
+
150
157
val pluginInstance = ModelixAuthorizationPluginInstance (config)
151
158
return pluginInstance
152
159
}
@@ -155,16 +162,36 @@ object ModelixAuthorization : BaseRouteScopedPlugin<IModelixAuthorizationConfig,
155
162
}
156
163
157
164
class ModelixAuthorizationPluginInstance (val config : ModelixAuthorizationConfig ) {
165
+
166
+ private val deniedPermissionRequests: MutableSet <DeniedPermissionRequest > = Collections .synchronizedSet(LinkedHashSet ())
158
167
private val permissionCache = CacheBuilder .newBuilder()
159
168
.expireAfterWrite(5 , TimeUnit .SECONDS )
160
169
.build<Pair <AccessTokenPrincipal , PermissionParts >, Boolean > ()
161
170
171
+ fun getDeniedPermissions (): Set <DeniedPermissionRequest > = deniedPermissionRequests.toSet()
172
+
162
173
fun hasPermission (call : ApplicationCall , permissionToCheck : PermissionParts ): Boolean {
163
174
if (! config.permissionCheckingEnabled()) return true
164
175
165
176
val principal = call.principal<AccessTokenPrincipal >() ? : throw NotLoggedInException ()
166
177
return permissionCache.get(principal to permissionToCheck) {
167
- getPermissionEvaluator(principal).hasPermission(permissionToCheck)
178
+ getPermissionEvaluator(principal).hasPermission(permissionToCheck).also { granted ->
179
+ if (! granted) {
180
+ val userId = principal.getUserName()
181
+ if (userId != null ) {
182
+ synchronized(deniedPermissionRequests) {
183
+ deniedPermissionRequests + = DeniedPermissionRequest (
184
+ permissionId = permissionToCheck,
185
+ userId = userId,
186
+ jwtPayload = principal.jwt.payload,
187
+ )
188
+ while (deniedPermissionRequests.size >= 100 ) {
189
+ deniedPermissionRequests.iterator().also { it.next() }.remove()
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
168
195
}
169
196
}
170
197
@@ -191,6 +218,14 @@ class ModelixAuthorizationPluginInstance(val config: ModelixAuthorizationConfig)
191
218
}
192
219
}
193
220
221
+ data class DeniedPermissionRequest (
222
+ val permissionId : PermissionParts ,
223
+ val userId : String ,
224
+ val jwtPayload : String ,
225
+ ) {
226
+ fun jwtPayloadJson () = String (Base64 .getUrlDecoder().decode(jwtPayload), StandardCharsets .UTF_8 )
227
+ }
228
+
194
229
/* *
195
230
* Returns an [JWTVerifier] that wraps our common authorization logic,
196
231
* so that it can be configured in the verification with Ktor's JWT authorization.
0 commit comments