@@ -18,6 +18,7 @@ import com.auth0.jwk.JwkProviderBuilder
1818import com.auth0.jwt.JWT
1919import com.auth0.jwt.interfaces.DecodedJWT
2020import com.google.common.cache.CacheBuilder
21+ import org.keycloak.authorization.client.AuthorizationDeniedException
2122import org.keycloak.authorization.client.AuthzClient
2223import org.keycloak.authorization.client.Configuration
2324import org.keycloak.authorization.client.resource.ProtectedResource
@@ -56,7 +57,7 @@ object KeycloakUtils {
5657
5758 private val permissionCache = CacheBuilder .newBuilder()
5859 .expireAfterWrite(30 , TimeUnit .SECONDS )
59- .build<String , List < Permission > >()
60+ .build<Pair < Pair < DecodedJWT , KeycloakResource >, KeycloakScope > , Boolean > ()
6061 private val existingResources = CacheBuilder .newBuilder()
6162 .expireAfterWrite(3 , TimeUnit .MINUTES )
6263 .build<String , ResourceRepresentation >()
@@ -90,44 +91,61 @@ object KeycloakUtils {
9091 }
9192 }
9293
93- @Synchronized
94- fun getPermissions (accessToken : DecodedJWT ): List <Permission > {
95- return permissionCache.get(accessToken.token) { queryPermissions(accessToken) }
96- }
97-
9894 fun getServiceAccountToken (): DecodedJWT {
9995 return JWT .decode(authzClient.obtainAccessToken().token)
10096 }
10197
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} " }
103105 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
112107 val introspect = authzClient.protection().introspectRequestingPartyToken(rpt)
113- return introspect.permissions
108+ return introspect.permissions ? : emptyList()
114109 } catch (e: Exception ) {
115110 throw RuntimeException (" Can't get permissions for token: ${token.token} " , e)
116111 }
117112 }
118113
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+
119122 @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+ }
129148 }
130- return scopes.contains(scope.name)
131149 }
132150
133151 @Synchronized
0 commit comments