@@ -18,6 +18,7 @@ import com.auth0.jwk.JwkProviderBuilder
18
18
import com.auth0.jwt.JWT
19
19
import com.auth0.jwt.interfaces.DecodedJWT
20
20
import com.google.common.cache.CacheBuilder
21
+ import org.keycloak.authorization.client.AuthorizationDeniedException
21
22
import org.keycloak.authorization.client.AuthzClient
22
23
import org.keycloak.authorization.client.Configuration
23
24
import org.keycloak.authorization.client.resource.ProtectedResource
@@ -56,7 +57,7 @@ object KeycloakUtils {
56
57
57
58
private val permissionCache = CacheBuilder .newBuilder()
58
59
.expireAfterWrite(30 , TimeUnit .SECONDS )
59
- .build<String , List < Permission > >()
60
+ .build<Pair < Pair < DecodedJWT , KeycloakResource >, KeycloakScope > , Boolean > ()
60
61
private val existingResources = CacheBuilder .newBuilder()
61
62
.expireAfterWrite(3 , TimeUnit .MINUTES )
62
63
.build<String , ResourceRepresentation >()
@@ -90,44 +91,61 @@ object KeycloakUtils {
90
91
}
91
92
}
92
93
93
- @Synchronized
94
- fun getPermissions (accessToken : DecodedJWT ): List <Permission > {
95
- return permissionCache.get(accessToken.token) { queryPermissions(accessToken) }
96
- }
97
-
98
94
fun getServiceAccountToken (): DecodedJWT {
99
95
return JWT .decode(authzClient.obtainAccessToken().token)
100
96
}
101
97
102
- private fun queryPermissions (token : DecodedJWT ): List <Permission > {
98
+ private fun isAccessToken (token : DecodedJWT ): Boolean {
99
+ val authClaim = token.getClaim(" authorization" )
100
+ return ! (authClaim.isNull || authClaim.isMissing)
101
+ }
102
+
103
+ private fun readPermissions (token : DecodedJWT ): List <Permission > {
104
+ require(isAccessToken(token)) { " Not an access token: ${token.token} " }
103
105
try {
104
- val rpt = if (token.getClaim(" authorization" ).isNull) {
105
- // The token was probably created by the OAuth proxy after login.
106
- // The token contains no permission information yet. Query them from keycloak.
107
- authzClient.authorization(token.token).authorize(AuthorizationRequest ()).token
108
- } else {
109
- // The token was probably created using KeycloakUtils.createToken
110
- token.token
111
- }
106
+ val rpt = token.token
112
107
val introspect = authzClient.protection().introspectRequestingPartyToken(rpt)
113
- return introspect.permissions
108
+ return introspect.permissions ? : emptyList()
114
109
} catch (e: Exception ) {
115
110
throw RuntimeException (" Can't get permissions for token: ${token.token} " , e)
116
111
}
117
112
}
118
113
114
+ private fun createAccessToken (identityToken : DecodedJWT , permissions : List <Pair <String , List <String >>>): DecodedJWT {
115
+ return JWT .decode(authzClient.authorization(identityToken.token).authorize(AuthorizationRequest ().also {
116
+ for (permission in permissions) {
117
+ it.addPermission(permission.first, permission.second)
118
+ }
119
+ }).token)
120
+ }
121
+
119
122
@Synchronized
120
- fun hasPermission (accessToken : DecodedJWT , resourceSpec : KeycloakResource , scope : KeycloakScope ): Boolean {
121
- ensureResourcesExists(resourceSpec, accessToken)
122
- val allPermissions = getPermissions(accessToken)
123
- val forResource = allPermissions.filter { it.resourceName == resourceSpec.name }
124
- if (forResource.isEmpty()) return false
125
- val scopes: Set <String > = forResource.mapNotNull { it.scopes }.flatten().toSet()
126
- if (scopes.isEmpty()) {
127
- // If the permissions are not restricted to any scope we assume they are valid for all scopes.
128
- return true
123
+ fun hasPermission (identityOrAccessToken : DecodedJWT , resourceSpec : KeycloakResource , scope : KeycloakScope ): Boolean {
124
+ val key = identityOrAccessToken to resourceSpec to scope
125
+ return permissionCache.get(key) { checkPermission(identityOrAccessToken, resourceSpec, scope) }
126
+ }
127
+
128
+ private fun checkPermission (identityOrAccessToken : DecodedJWT , resourceSpec : KeycloakResource , scope : KeycloakScope ): Boolean {
129
+ ensureResourcesExists(resourceSpec, identityOrAccessToken)
130
+
131
+ if (isAccessToken(identityOrAccessToken)) {
132
+ val grantedPermissions = readPermissions(identityOrAccessToken)
133
+ val forResource = grantedPermissions.filter { it.resourceName == resourceSpec.name }
134
+ if (forResource.isEmpty()) return false
135
+ val scopes: Set <String > = forResource.mapNotNull { it.scopes }.flatten().toSet()
136
+ if (scopes.isEmpty()) {
137
+ // If the permissions are not restricted to any scope we assume they are valid for all scopes.
138
+ return true
139
+ }
140
+ return scopes.contains(scope.name)
141
+ } else {
142
+ return try {
143
+ createAccessToken(identityOrAccessToken, listOf (resourceSpec.name to listOf (scope.name)))
144
+ true
145
+ } catch (_: AuthorizationDeniedException ) {
146
+ false
147
+ }
129
148
}
130
- return scopes.contains(scope.name)
131
149
}
132
150
133
151
@Synchronized
0 commit comments