keycloak-memoizeit

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index add1c83..3ae286f 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -129,7 +129,7 @@ public abstract class AbstractPolicyEnforcer {
             Set<String> allowedScopes = permission.getScopes();
 
             if (permission.getResourceSetId() != null) {
-                if (permission.getResourceSetId().equals(actualPathConfig.getId())) {
+                if (isResourcePermission(actualPathConfig, permission)) {
                     if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
                         LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
                         if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
@@ -211,6 +211,7 @@ public abstract class AbstractPolicyEnforcer {
                 config.setScopes(originalConfig.getScopes());
                 config.setMethods(originalConfig.getMethods());
                 config.setInstance(true);
+                config.setParentConfig(originalConfig);
 
                 this.paths.add(config);
 
@@ -240,4 +241,16 @@ public abstract class AbstractPolicyEnforcer {
     private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
         return new AuthorizationContext(accessToken, this.paths);
     }
+
+    private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
+        // first we try a match using resource id
+        boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
+
+        // as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
+        if (!resourceMatch && actualPathConfig.isInstance()) {
+            resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getParentConfig().getId());
+        }
+
+        return resourceMatch;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
index 98a4045..5145ec7 100644
--- a/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/PolicyEnforcerConfig.java
@@ -17,6 +17,7 @@
  */
 package org.keycloak.representations.adapters.config;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 import java.util.ArrayList;
@@ -99,6 +100,9 @@ public class PolicyEnforcerConfig {
         private String id;
         private boolean instance;
 
+        @JsonIgnore
+        private PathConfig parentConfig;
+
         public String getPath() {
             return this.path;
         }
@@ -169,6 +173,14 @@ public class PolicyEnforcerConfig {
         public void setInstance(boolean instance) {
             this.instance = instance;
         }
+
+        public void setParentConfig(PathConfig parentConfig) {
+            this.parentConfig = parentConfig;
+        }
+
+        public PathConfig getParentConfig() {
+            return parentConfig;
+        }
     }
 
     public static class MethodConfig {
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index eaa8c78..ad154a6 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -106,7 +106,10 @@ public class AuthorizationTokenService {
                 List<Permission> entitlements = Permissions.allPermits(results);
 
                 if (entitlements.isEmpty()) {
-                    asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN));
+                    asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.FORBIDDEN)
+                            .entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
+                            .allowedOrigins(identity.getAccessToken())
+                            .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
                 } else {
                     AuthorizationResponse response = new AuthorizationResponse(createRequestingPartyToken(entitlements, identity.getAccessToken()));
                     asyncResponse.resume(Cors.add(httpRequest, Response.status(Status.CREATED).entity(response)).allowedOrigins(identity.getAccessToken())
@@ -217,12 +220,14 @@ public class AuthorizationTokenService {
     }
 
     private PermissionTicket verifyPermissionTicket(AuthorizationRequest request) {
-        if (!Tokens.verifySignature(request.getTicket(), getRealm().getPublicKey())) {
+        String ticketString = request.getTicket();
+
+        if (ticketString == null || !Tokens.verifySignature(ticketString, getRealm().getPublicKey())) {
             throw new ErrorResponseException("invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
         }
 
         try {
-            PermissionTicket ticket = new JWSInput(request.getTicket()).readJsonContent(PermissionTicket.class);
+            PermissionTicket ticket = new JWSInput(ticketString).readJsonContent(PermissionTicket.class);
 
             if (!ticket.isActive()) {
                 throw new ErrorResponseException("invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
diff --git a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
index 983646b..df6f54d 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -80,8 +80,9 @@ public class EntitlementService {
         this.authorization = authorization;
     }
 
+    @Path("{resource_server_id}")
     @OPTIONS
-    public Response authorizePreFlight() {
+    public Response authorizePreFlight(@PathParam("resource_server_id") String resourceServerId) {
         return Cors.add(this.request, Response.ok()).auth().preflight().build();
     }
 
@@ -118,7 +119,10 @@ public class EntitlementService {
                 List<Permission> entitlements = Permissions.allPermits(results);
 
                 if (entitlements.isEmpty()) {
-                    asyncResponse.resume(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN));
+                    asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN)
+                            .entity(new ErrorResponseException("not_authorized", "Authorization denied.", Status.FORBIDDEN)))
+                            .allowedOrigins(identity.getAccessToken())
+                            .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
                 } else {
                     asyncResponse.resume(Cors.add(request, Response.ok().entity(new EntitlementResponse(createRequestingPartyToken(entitlements)))).allowedOrigins(identity.getAccessToken()).allowedMethods("GET").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build());
                 }
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 240fafa..43204b8 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -26,6 +26,7 @@ import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.permission.ResourcePermission;
 import org.keycloak.authorization.policy.evaluation.Result;
+import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.representations.authorization.Permission;
 
@@ -58,9 +59,10 @@ public final class Permissions {
     public static List<ResourcePermission> all(ResourceServer resourceServer, Identity identity, AuthorizationProvider authorization) {
         List<ResourcePermission> permissions = new ArrayList<>();
         StoreFactory storeFactory = authorization.getStoreFactory();
+        ResourceStore resourceStore = storeFactory.getResourceStore();
 
-        storeFactory.getResourceStore().findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
-        storeFactory.getResourceStore().findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
+        resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
+        resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
 
         return permissions;
     }