diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java index b1e668aee3..b0b9847ce9 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java @@ -17,6 +17,8 @@ */ package org.keycloak.authorization.policy.provider.aggregated; +import java.util.List; + import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.DecisionResultCollector; @@ -26,21 +28,11 @@ import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; -import java.util.List; - /** * @author Pedro Igor */ public class AggregatePolicyProvider implements PolicyProvider { - private final Policy policy; - private final AuthorizationProvider authorization; - - public AggregatePolicyProvider(Policy policy, AuthorizationProvider authorization) { - this.policy = policy; - this.authorization = authorization; - } - @Override public void evaluate(Evaluation evaluation) { //TODO: need to detect deep recursions @@ -59,10 +51,12 @@ public class AggregatePolicyProvider implements PolicyProvider { } }; - this.policy.getAssociatedPolicies().forEach(associatedPolicy -> { - PolicyProviderFactory providerFactory = authorization.getProviderFactory(associatedPolicy.getType()); - PolicyProvider policyProvider = providerFactory.create(associatedPolicy, authorization); - policyProvider.evaluate(new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision)); + Policy policy = evaluation.getPolicy(); + AuthorizationProvider authorization = evaluation.getAuthorizationProvider(); + + policy.getAssociatedPolicies().forEach(associatedPolicy -> { + PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType()); + policyProvider.evaluate(new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization)); }); decision.onComplete(); diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java index 9c7faad5f8..3e8697335a 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProviderFactory.java @@ -19,7 +19,6 @@ package org.keycloak.authorization.policy.provider.aggregated; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; @@ -32,6 +31,8 @@ import org.keycloak.models.KeycloakSessionFactory; */ public class AggregatePolicyProviderFactory implements PolicyProviderFactory { + private AggregatePolicyProvider provider = new AggregatePolicyProvider(); + @Override public String getName() { return "Aggregated"; @@ -43,8 +44,8 @@ public class AggregatePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new AggregatePolicyProvider(policy, authorization); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java index ec84bbcac1..5c778d810c 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java @@ -1,5 +1,7 @@ package org.keycloak.authorization.policy.provider.client; +import static org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory.getClients; + import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; @@ -8,26 +10,19 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; -import static org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory.getClients; - public class ClientPolicyProvider implements PolicyProvider { - private final Policy policy; - private final AuthorizationProvider authorization; - - public ClientPolicyProvider(Policy policy, AuthorizationProvider authorization) { - this.policy = policy; - this.authorization = authorization; - } - @Override public void evaluate(Evaluation evaluation) { + Policy policy = evaluation.getPolicy(); EvaluationContext context = evaluation.getContext(); - String[] clients = getClients(this.policy); + String[] clients = getClients(policy); + AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider(); + RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm(); if (clients.length > 0) { for (String client : clients) { - ClientModel clientModel = getCurrentRealm().getClientById(client); + ClientModel clientModel = realm.getClientById(client); if (context.getAttributes().containsValue("kc.client.id", clientModel.getClientId())) { evaluation.grant(); return; @@ -40,8 +35,4 @@ public class ClientPolicyProvider implements PolicyProvider { public void close() { } - - private RealmModel getCurrentRealm() { - return this.authorization.getKeycloakSession().getContext().getRealm(); - } } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java index e800a5bd50..8cb00293a4 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java @@ -1,5 +1,9 @@ package org.keycloak.authorization.policy.provider.client; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; @@ -8,18 +12,18 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel.ClientRemovedEvent; import org.keycloak.util.JsonSerialization; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - public class ClientPolicyProviderFactory implements PolicyProviderFactory { + private ClientPolicyProvider provider = new ClientPolicyProvider(); + @Override public String getName() { return "Client"; @@ -31,8 +35,8 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new ClientPolicyProvider(policy, authorization); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override @@ -56,31 +60,36 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory { if (event instanceof ClientRemovedEvent) { KeycloakSession keycloakSession = ((ClientRemovedEvent) event).getKeycloakSession(); AuthorizationProvider provider = keycloakSession.getProvider(AuthorizationProvider.class); - PolicyStore policyStore = provider.getStoreFactory().getPolicyStore(); + StoreFactory storeFactory = provider.getStoreFactory(); + PolicyStore policyStore = storeFactory.getPolicyStore(); ClientModel removedClient = ((ClientRemovedEvent) event).getClient(); + ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore(); + ResourceServer resourceServer = resourceServerStore.findByClient(removedClient.getId()); - policyStore.findByType(getId()).forEach(policy -> { - List clients = new ArrayList<>(); + if (resourceServer != null) { + policyStore.findByType(getId(), resourceServer.getId()).forEach(policy -> { + List clients = new ArrayList<>(); - for (String clientId : getClients(policy)) { - if (!clientId.equals(removedClient.getId())) { - clients.add(clientId); + for (String clientId : getClients(policy)) { + if (!clientId.equals(removedClient.getId())) { + clients.add(clientId); + } } - } - try { - if (clients.isEmpty()) { - policyStore.findDependentPolicies(policy.getId()).forEach(dependentPolicy -> { - dependentPolicy.removeAssociatedPolicy(policy); - }); - policyStore.delete(policy.getId()); - } else { - policy.getConfig().put("clients", JsonSerialization.writeValueAsString(clients)); + try { + if (clients.isEmpty()) { + policyStore.findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { + dependentPolicy.removeAssociatedPolicy(policy); + }); + policyStore.delete(policy.getId()); + } else { + policy.getConfig().put("clients", JsonSerialization.writeValueAsString(clients)); + } + } catch (IOException e) { + throw new RuntimeException("Error while synchronizing clients with policy [" + policy.getName() + "].", e); } - } catch (IOException e) { - throw new RuntimeException("Error while synchronizing clients with policy [" + policy.getName() + "].", e); - } - }); + }); + } } }); } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java index eeeb3ea2b4..f87573136b 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java @@ -17,32 +17,34 @@ */ package org.keycloak.authorization.policy.provider.js; +import java.util.function.Supplier; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; + import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; - /** * @author Pedro Igor */ public class JSPolicyProvider implements PolicyProvider { - private final Policy policy; + private Supplier engineProvider; - public JSPolicyProvider(Policy policy) { - this.policy = policy; + public JSPolicyProvider(Supplier engineProvider) { + this.engineProvider = engineProvider; } @Override public void evaluate(Evaluation evaluation) { - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("nashorn"); + ScriptEngine engine = engineProvider.get(); engine.put("$evaluation", evaluation); + Policy policy = evaluation.getPolicy(); + try { engine.eval(policy.getConfig().get("code")); } catch (ScriptException e) { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java index 8134d95889..b4a5099ebd 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProviderFactory.java @@ -1,8 +1,12 @@ package org.keycloak.authorization.policy.provider.js; +import java.util.function.Supplier; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; @@ -15,6 +19,13 @@ import org.keycloak.models.KeycloakSessionFactory; */ public class JSPolicyProviderFactory implements PolicyProviderFactory { + private JSPolicyProvider provider = new JSPolicyProvider(new Supplier() { + @Override + public ScriptEngine get() { + return new ScriptEngineManager().getEngineByName("nashorn"); + } + }); + @Override public String getName() { return "JavaScript"; @@ -26,8 +37,8 @@ public class JSPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new JSPolicyProvider(policy); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProvider.java index c76a9899b6..69e93fe454 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProvider.java @@ -17,7 +17,6 @@ */ package org.keycloak.authorization.policy.provider.resource; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; @@ -26,7 +25,7 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; */ public class ResourcePolicyProvider implements PolicyProvider { - public ResourcePolicyProvider(Policy policy) { + public ResourcePolicyProvider() { } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java index 8ea4800b7f..d7a6b2bc0e 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/resource/ResourcePolicyProviderFactory.java @@ -2,7 +2,6 @@ package org.keycloak.authorization.policy.provider.resource; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; @@ -15,6 +14,8 @@ import org.keycloak.models.KeycloakSessionFactory; */ public class ResourcePolicyProviderFactory implements PolicyProviderFactory { + private ResourcePolicyProvider provider = new ResourcePolicyProvider(); + @Override public String getName() { return "Resource-Based"; @@ -26,8 +27,8 @@ public class ResourcePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new ResourcePolicyProvider(policy); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java index 9fb9787f4b..4aafedd9e4 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java @@ -17,6 +17,10 @@ */ package org.keycloak.authorization.policy.provider.role; +import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles; + +import java.util.Map; + import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.identity.Identity; import org.keycloak.authorization.model.Policy; @@ -26,39 +30,26 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import java.util.Map; - -import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles; - /** * @author Pedro Igor */ public class RolePolicyProvider implements PolicyProvider { - private final Policy policy; - private final AuthorizationProvider authorization; - - public RolePolicyProvider(Policy policy, AuthorizationProvider authorization) { - this.policy = policy; - this.authorization = authorization; - } - - public RolePolicyProvider() { - this(null, null); - } - @Override public void evaluate(Evaluation evaluation) { - Map[] roleIds = getRoles(this.policy); + Policy policy = evaluation.getPolicy(); + Map[] roleIds = getRoles(policy); + AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider(); + RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm(); if (roleIds.length > 0) { Identity identity = evaluation.getContext().getIdentity(); for (Map current : roleIds) { - RoleModel role = getCurrentRealm().getRoleById((String) current.get("id")); + RoleModel role = realm.getRoleById((String) current.get("id")); if (role != null) { - boolean hasRole = hasRole(identity, role); + boolean hasRole = hasRole(identity, role, realm); if (!hasRole && Boolean.valueOf(isRequired(current))) { evaluation.deny(); @@ -75,19 +66,15 @@ public class RolePolicyProvider implements PolicyProvider { return (boolean) current.getOrDefault("required", false); } - private boolean hasRole(Identity identity, RoleModel role) { + private boolean hasRole(Identity identity, RoleModel role, RealmModel realm) { String roleName = role.getName(); if (role.isClientRole()) { - ClientModel clientModel = getCurrentRealm().getClientById(role.getContainerId()); + ClientModel clientModel = realm.getClientById(role.getContainerId()); return identity.hasClientRole(clientModel.getClientId(), roleName); } return identity.hasRealmRole(roleName); } - private RealmModel getCurrentRealm() { - return this.authorization.getKeycloakSession().getContext().getRealm(); - } - @Override public void close() { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java index 67de87a7cb..33db2d5473 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProviderFactory.java @@ -26,8 +26,13 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleContainerModel.RoleRemovedEvent; import org.keycloak.models.RoleModel; import org.keycloak.util.JsonSerialization; @@ -37,12 +42,15 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * @author Pedro Igor */ public class RolePolicyProviderFactory implements PolicyProviderFactory { + private RolePolicyProvider provider = new RolePolicyProvider(); + @Override public String getName() { return "Role"; @@ -54,8 +62,8 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new RolePolicyProvider(policy, authorization); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override @@ -79,43 +87,63 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory { if (event instanceof RoleRemovedEvent) { KeycloakSession keycloakSession = ((RoleRemovedEvent) event).getKeycloakSession(); AuthorizationProvider provider = keycloakSession.getProvider(AuthorizationProvider.class); - PolicyStore policyStore = provider.getStoreFactory().getPolicyStore(); + StoreFactory storeFactory = provider.getStoreFactory(); + PolicyStore policyStore = storeFactory.getPolicyStore(); RoleModel removedRole = ((RoleRemovedEvent) event).getRole(); + RoleContainerModel container = removedRole.getContainer(); + ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore(); - policyStore.findByType(getId()).forEach(policy -> { - List roles = new ArrayList<>(); - - for (Map role : getRoles(policy)) { - if (!role.get("id").equals(removedRole.getId())) { - Map updated = new HashMap(); - updated.put("id", role.get("id")); - Object required = role.get("required"); - if (required != null) { - updated.put("required", required); - } - roles.add(updated); - } - } - - try { - if (roles.isEmpty()) { - policyStore.findDependentPolicies(policy.getId()).forEach(dependentPolicy -> { - dependentPolicy.removeAssociatedPolicy(policy); - }); - policyStore.delete(policy.getId()); - } else { - Map config = policy.getConfig(); - config.put("roles", JsonSerialization.writeValueAsString(roles)); - policy.setConfig(config); - } - } catch (IOException e) { - throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e); - } - }); + if (container instanceof RealmModel) { + RealmModel realm = (RealmModel) container; + realm.getClients().forEach(clientModel -> updateResourceServer(clientModel, removedRole, resourceServerStore, policyStore)); + } else { + ClientModel clientModel = (ClientModel) container; + updateResourceServer(clientModel, removedRole, resourceServerStore, policyStore); + } } }); } + private void updateResourceServer(ClientModel clientModel, RoleModel removedRole, ResourceServerStore resourceServerStore, PolicyStore policyStore) { + ResourceServer resourceServer = resourceServerStore.findByClient(clientModel.getId()); + + if (resourceServer != null) { + policyStore.findByType(getId(), resourceServer.getId()).forEach(policy -> { + List roles = new ArrayList<>(); + + for (Map role : getRoles(policy)) { + if (!role.get("id").equals(removedRole.getId())) { + Map updated = new HashMap(); + updated.put("id", role.get("id")); + Object required = role.get("required"); + if (required != null) { + updated.put("required", required); + } + roles.add(updated); + } + } + + try { + if (roles.isEmpty()) { + policyStore.findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { + dependentPolicy.removeAssociatedPolicy(policy); + if (dependentPolicy.getAssociatedPolicies().isEmpty()) { + policyStore.delete(dependentPolicy.getId()); + } + }); + policyStore.delete(policy.getId()); + } else { + Map config = policy.getConfig(); + config.put("roles", JsonSerialization.writeValueAsString(roles)); + policy.setConfig(config); + } + } catch (IOException e) { + throw new RuntimeException("Error while synchronizing roles with policy [" + policy.getName() + "].", e); + } + }); + } + } + @Override public void close() { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProvider.java index 5c10cc3d5d..0a8cf859f3 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProvider.java @@ -17,7 +17,6 @@ */ package org.keycloak.authorization.policy.provider.scope; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; @@ -26,12 +25,6 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; */ public class ScopePolicyProvider implements PolicyProvider { - private final Policy policy; - - public ScopePolicyProvider(Policy policy) { - this.policy = policy; - } - @Override public void evaluate(Evaluation evaluation) { } diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java index 6ed0cd5865..0678eb3152 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/scope/ScopePolicyProviderFactory.java @@ -2,7 +2,6 @@ package org.keycloak.authorization.policy.provider.scope; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; @@ -15,6 +14,8 @@ import org.keycloak.models.KeycloakSessionFactory; */ public class ScopePolicyProviderFactory implements PolicyProviderFactory { + private ScopePolicyProvider provider = new ScopePolicyProvider(); + @Override public String getName() { return "Scope-Based"; @@ -26,8 +27,8 @@ public class ScopePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new ScopePolicyProvider(policy); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java index 84ba1a2889..7ce4c6ea35 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java @@ -17,14 +17,14 @@ */ package org.keycloak.authorization.policy.provider.time; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.policy.evaluation.Evaluation; -import org.keycloak.authorization.policy.provider.PolicyProvider; - import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.policy.evaluation.Evaluation; +import org.keycloak.authorization.policy.provider.PolicyProvider; + /** * @author Pedro Igor */ @@ -32,51 +32,51 @@ public class TimePolicyProvider implements PolicyProvider { static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd hh:mm:ss"; - private final Policy policy; private final SimpleDateFormat dateFormat; private final Date currentDate; - public TimePolicyProvider(Policy policy) { - this.policy = policy; + public TimePolicyProvider() { this.dateFormat = new SimpleDateFormat(DEFAULT_DATE_PATTERN); this.currentDate = new Date(); } @Override public void evaluate(Evaluation evaluation) { + Policy policy = evaluation.getPolicy(); + try { - String notBefore = this.policy.getConfig().get("nbf"); - if (notBefore != null) { + String notBefore = policy.getConfig().get("nbf"); + if (notBefore != null && !"".equals(notBefore)) { if (this.currentDate.before(this.dateFormat.parse(format(notBefore)))) { evaluation.deny(); return; } } - String notOnOrAfter = this.policy.getConfig().get("noa"); - if (notOnOrAfter != null) { + String notOnOrAfter = policy.getConfig().get("noa"); + if (notOnOrAfter != null && !"".equals(notOnOrAfter)) { if (this.currentDate.after(this.dateFormat.parse(format(notOnOrAfter)))) { evaluation.deny(); return; } } - if (isInvalid(Calendar.DAY_OF_MONTH, "dayMonth") - || isInvalid(Calendar.MONTH, "month") - || isInvalid(Calendar.YEAR, "year") - || isInvalid(Calendar.HOUR_OF_DAY, "hour") - || isInvalid(Calendar.MINUTE, "minute")) { + if (isInvalid(Calendar.DAY_OF_MONTH, "dayMonth", policy) + || isInvalid(Calendar.MONTH, "month", policy) + || isInvalid(Calendar.YEAR, "year", policy) + || isInvalid(Calendar.HOUR_OF_DAY, "hour", policy) + || isInvalid(Calendar.MINUTE, "minute", policy)) { evaluation.deny(); return; } evaluation.grant(); } catch (Exception e) { - throw new RuntimeException("Could not evaluate time-based policy [" + this.policy.getName() + "].", e); + throw new RuntimeException("Could not evaluate time-based policy [" + policy.getName() + "].", e); } } - private boolean isInvalid(int timeConstant, String configName) { + private boolean isInvalid(int timeConstant, String configName, Policy policy) { Calendar calendar = Calendar.getInstance(); calendar.setTime(this.currentDate); @@ -87,9 +87,9 @@ public class TimePolicyProvider implements PolicyProvider { dateField++; } - String start = this.policy.getConfig().get(configName); + String start = policy.getConfig().get(configName); if (start != null) { - String end = this.policy.getConfig().get(configName + "End"); + String end = policy.getConfig().get(configName + "End"); if (end != null) { if (dateField < Integer.parseInt(start) || dateField > Integer.parseInt(end)) { return true; diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java index efe3cd2705..94c5aad754 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProviderFactory.java @@ -2,7 +2,6 @@ package org.keycloak.authorization.policy.provider.time; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; @@ -15,6 +14,8 @@ import org.keycloak.models.KeycloakSessionFactory; */ public class TimePolicyProviderFactory implements PolicyProviderFactory { + private TimePolicyProvider provider = new TimePolicyProvider(); + @Override public String getName() { return "Time"; @@ -26,8 +27,8 @@ public class TimePolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new TimePolicyProvider(policy); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java index a6fc0a48c3..2f77106504 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java @@ -29,16 +29,11 @@ import static org.keycloak.authorization.policy.provider.user.UserPolicyProvider */ public class UserPolicyProvider implements PolicyProvider { - private final Policy policy; - - public UserPolicyProvider(Policy policy) { - this.policy = policy; - } - @Override public void evaluate(Evaluation evaluation) { + Policy policy = evaluation.getPolicy(); EvaluationContext context = evaluation.getContext(); - String[] userIds = getUsers(this.policy); + String[] userIds = getUsers(policy); if (userIds.length > 0) { for (String userId : userIds) { diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java index fdeeac0b17..09345ec8e3 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java @@ -18,6 +18,10 @@ package org.keycloak.authorization.policy.provider.user; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; @@ -26,21 +30,22 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.UserRemovedEvent; import org.keycloak.util.JsonSerialization; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - /** * @author Pedro Igor */ public class UserPolicyProviderFactory implements PolicyProviderFactory { + private UserPolicyProvider provider = new UserPolicyProvider(); + @Override public String getName() { return "User"; @@ -52,8 +57,8 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new UserPolicyProvider(policy); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override @@ -77,29 +82,40 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory { if (event instanceof UserRemovedEvent) { KeycloakSession keycloakSession = ((UserRemovedEvent) event).getKeycloakSession(); AuthorizationProvider provider = keycloakSession.getProvider(AuthorizationProvider.class); - PolicyStore policyStore = provider.getStoreFactory().getPolicyStore(); + StoreFactory storeFactory = provider.getStoreFactory(); + PolicyStore policyStore = storeFactory.getPolicyStore(); UserModel removedUser = ((UserRemovedEvent) event).getUser(); + RealmModel realm = ((UserRemovedEvent) event).getRealm(); + ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore(); + realm.getClients().forEach(clientModel -> { + ResourceServer resourceServer = resourceServerStore.findByClient(clientModel.getId()); - policyStore.findByType(getId()).forEach(policy -> { - List users = new ArrayList<>(); + if (resourceServer != null) { + policyStore.findByType(getId(), resourceServer.getId()).forEach(policy -> { + List users = new ArrayList<>(); - for (String userId : getUsers(policy)) { - if (!userId.equals(removedUser.getId())) { - users.add(userId); - } - } + for (String userId : getUsers(policy)) { + if (!userId.equals(removedUser.getId())) { + users.add(userId); + } + } - try { - if (users.isEmpty()) { - policyStore.findDependentPolicies(policy.getId()).forEach(dependentPolicy -> { - dependentPolicy.removeAssociatedPolicy(policy); - }); - policyStore.delete(policy.getId()); - } else { - policy.getConfig().put("users", JsonSerialization.writeValueAsString(users)); - } - } catch (IOException e) { - throw new RuntimeException("Error while synchronizing users with policy [" + policy.getName() + "].", e); + try { + if (users.isEmpty()) { + policyStore.findDependentPolicies(policy.getId(), resourceServer.getId()).forEach(dependentPolicy -> { + dependentPolicy.removeAssociatedPolicy(policy); + if (dependentPolicy.getAssociatedPolicies().isEmpty()) { + policyStore.delete(dependentPolicy.getId()); + } + }); + policyStore.delete(policy.getId()); + } else { + policy.getConfig().put("users", JsonSerialization.writeValueAsString(users)); + } + } catch (IOException e) { + throw new RuntimeException("Error while synchronizing users with policy [" + policy.getName() + "].", e); + } + }); } }); } diff --git a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProvider.java b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProvider.java index c53e3612c8..77023f528e 100644 --- a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProvider.java +++ b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProvider.java @@ -17,6 +17,9 @@ */ package org.keycloak.authorization.policy.provider.drools; +import java.util.function.Function; + +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; @@ -25,15 +28,15 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; */ public class DroolsPolicyProvider implements PolicyProvider { - private final DroolsPolicy policy; + private final Function policy; - public DroolsPolicyProvider(DroolsPolicy policy) { - this.policy = policy; + public DroolsPolicyProvider(Function policyProvider) { + this.policy = policyProvider; } @Override - public void evaluate(Evaluation evaluationt) { - this.policy.evaluate(evaluationt); + public void evaluate(Evaluation evaluation) { + policy.apply(evaluation.getPolicy()).evaluate(evaluation); } @Override diff --git a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java index b3305aeb3e..74ed89d01a 100644 --- a/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java +++ b/authz/policy/drools/src/main/java/org/keycloak/authorization/policy/provider/drools/DroolsPolicyProviderFactory.java @@ -1,5 +1,9 @@ package org.keycloak.authorization.policy.provider.drools; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; @@ -9,24 +13,25 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.utils.PostMigrationEvent; -import org.keycloak.provider.ProviderEvent; -import org.keycloak.provider.ProviderEventListener; -import org.keycloak.provider.ProviderFactory; import org.kie.api.KieServices; import org.kie.api.KieServices.Factory; import org.kie.api.runtime.KieContainer; -import java.util.HashMap; -import java.util.Map; - /** * @author Pedro Igor */ public class DroolsPolicyProviderFactory implements PolicyProviderFactory { private KieServices ks; - private final Map containers = new HashMap<>(); + private final Map containers = Collections.synchronizedMap(new HashMap<>()); + private DroolsPolicyProvider provider = new DroolsPolicyProvider(policy -> { + if (!containers.containsKey(policy.getId())) { + synchronized (containers) { + update(policy); + } + } + return containers.get(policy.getId()); + }); @Override public String getName() { @@ -39,12 +44,8 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - if (!this.containers.containsKey(policy.getId())) { - update(policy); - } - - return new DroolsPolicyProvider(this.containers.get(policy.getId())); + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; } @Override @@ -64,19 +65,6 @@ public class DroolsPolicyProviderFactory implements PolicyProviderFactory { @Override public void postInit(KeycloakSessionFactory factory) { - factory.register(new ProviderEventListener() { - - @Override - public void onEvent(ProviderEvent event) { - // Ensure the initialization is done after DB upgrade is finished - if (event instanceof PostMigrationEvent) { - ProviderFactory providerFactory = factory.getProviderFactory(AuthorizationProvider.class); - AuthorizationProvider authorization = providerFactory.create(factory.create()); - authorization.getStoreFactory().getPolicyStore().findByType(getId()).forEach(DroolsPolicyProviderFactory.this::update); - } - } - - }); } @Override diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java index 9a51031782..dc049919d4 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/PolicyRepresentation.java @@ -16,11 +16,7 @@ */ package org.keycloak.representations.idm.authorization; -import com.fasterxml.jackson.annotation.JsonInclude; - -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; @@ -36,9 +32,6 @@ public class PolicyRepresentation { private Logic logic = Logic.POSITIVE; private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS; private Map config = new HashMap(); - private List dependentPolicies; - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List associatedPolicies = new ArrayList<>(); public String getId() { return this.id; @@ -96,14 +89,6 @@ public class PolicyRepresentation { this.description = description; } - public List getAssociatedPolicies() { - return associatedPolicies; - } - - public void setAssociatedPolicies(List associatedPolicies) { - this.associatedPolicies = associatedPolicies; - } - @Override public boolean equals(final Object o) { if (this == o) return true; @@ -116,12 +101,4 @@ public class PolicyRepresentation { public int hashCode() { return Objects.hash(getId()); } - - public void setDependentPolicies(List dependentPolicies) { - this.dependentPolicies = dependentPolicies; - } - - public List getDependentPolicies() { - return this.dependentPolicies; - } } \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java index 8f3c7957ef..86f6f98bed 100644 --- a/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ResourceRepresentation.java @@ -16,14 +16,14 @@ */ package org.keycloak.representations.idm.authorization; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - import java.net.URI; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Set; -import java.util.function.Predicate; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; /** *

One or more resources that the resource server manages as a set of protected resources. @@ -159,18 +159,6 @@ public class ResourceRepresentation { this.owner = owner; } - public List getPolicies() { - return this.policies; - } - - public void setPolicies(List policies) { - this.policies = policies; - } - - T test(Predicate t) { - return null; - } - public void setTypedScopes(List typedScopes) { this.typedScopes = typedScopes; } @@ -178,4 +166,15 @@ public class ResourceRepresentation { public List getTypedScopes() { return typedScopes; } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResourceRepresentation scope = (ResourceRepresentation) o; + return Objects.equals(getName(), scope.getName()); + } + + public int hashCode() { + return Objects.hash(getName()); + } } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PolicyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PolicyResource.java index 9a450452b5..28202f5676 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PolicyResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PolicyResource.java @@ -16,16 +16,21 @@ */ package org.keycloak.admin.client.resource; -import org.jboss.resteasy.annotations.cache.NoCache; -import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; +import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; + /** * @author Pedro Igor */ @@ -42,4 +47,28 @@ public interface PolicyResource { @DELETE void remove(); + + @Path("/associatedPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List associatedPolicies(); + + @Path("/dependentPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List dependentPolicies(); + + @Path("/scopes") + @GET + @Produces("application/json") + @NoCache + List scopes(); + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + List resources(); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceResource.java index 834cb0602e..28e57a7e27 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceResource.java @@ -16,13 +16,17 @@ */ package org.keycloak.admin.client.resource; +import java.util.List; + import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; +import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @@ -42,4 +46,10 @@ public interface ResourceResource { @DELETE void remove(); + + @Path("permissions") + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + List permissions(); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopeResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopeResource.java index 6975574111..87b285bb4d 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopeResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourceScopeResource.java @@ -16,13 +16,17 @@ */ package org.keycloak.admin.client.resource; +import java.util.List; + import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; +import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @@ -42,4 +46,9 @@ public interface ResourceScopeResource { @DELETE void remove(); + + @Path("/permissions") + @GET + @Produces(MediaType.APPLICATION_JSON) + List permissions(); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java index b503bcef77..c93465eaff 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java @@ -18,6 +18,17 @@ package org.keycloak.models.authorization.infinispan; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.infinispan.Cache; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; @@ -29,17 +40,10 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedPolicy; +import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; - /** * @author Pedro Igor */ @@ -47,41 +51,58 @@ public class CachedPolicyStore implements PolicyStore { private static final String POLICY_ID_CACHE_PREFIX = "policy-id-"; - private final Cache cache; + private final Cache> cache; private final KeycloakSession session; private final CacheTransaction transaction; + private final List cacheKeys; private StoreFactory storeFactory; private PolicyStore delegate; + private CachedStoreFactoryProvider cachedStoreFactory; - public CachedPolicyStore(KeycloakSession session, CacheTransaction transaction) { + public CachedPolicyStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { this.session = session; this.transaction = transaction; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); + cacheKeys = new ArrayList<>(); + cacheKeys.add("findByResource"); + cacheKeys.add("findByResourceType"); + cacheKeys.add("findByScopeIds"); + cacheKeys.add("findByType"); + this.storeFactory = storeFactory; } @Override public Policy create(String name, String type, ResourceServer resourceServer) { Policy policy = getDelegate().create(name, type, getStoreFactory().getResourceServerStore().findById(resourceServer.getId())); + String id = policy.getId(); - this.transaction.whenRollback(() -> cache.remove(getCacheKeyForPolicy(policy.getId()))); + this.transaction.whenCommit(() -> { + cache.remove(getCacheKeyForPolicy(id)); + invalidateCache(resourceServer.getId()); + }); return createAdapter(new CachedPolicy(policy)); } @Override public void delete(String id) { + Policy policy = findById(id, null); + ResourceServer resourceServer = policy.getResourceServer(); getDelegate().delete(id); - this.transaction.whenCommit(() -> cache.remove(getCacheKeyForPolicy(id))); + this.transaction.whenCommit(() -> { + cache.remove(getCacheKeyForPolicy(id)); + invalidateCache(resourceServer.getId()); + }); } @Override - public Policy findById(String id) { + public Policy findById(String id, String resourceServerId) { String cacheKeyForPolicy = getCacheKeyForPolicy(id); List cached = this.cache.get(cacheKeyForPolicy); if (cached == null) { - Policy policy = getDelegate().findById(id); + Policy policy = getDelegate().findById(id, resourceServerId); if (policy != null) { return createAdapter(updatePolicyCache(policy)); @@ -100,7 +121,7 @@ public class CachedPolicyStore implements PolicyStore { @Override public List findByResourceServer(String resourceServerId) { - return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList()); + return getDelegate().findByResourceServer(resourceServerId); } @Override @@ -109,88 +130,49 @@ public class CachedPolicyStore implements PolicyStore { } @Override - public List findByResource(String resourceId) { - List cache = new ArrayList<>(); - - for (Entry entry : this.cache.entrySet()) { - String cacheKey = (String) entry.getKey(); - - if (cacheKey.startsWith(POLICY_ID_CACHE_PREFIX)) { - List value = (List) entry.getValue(); - CachedPolicy policy = value.get(0); - - if (policy.getResourcesIds().contains(resourceId)) { - cache.add(findById(policy.getId())); - } - } - } - - if (cache.isEmpty()) { - getDelegate().findByResource(resourceId).forEach(policy -> cache.add(findById(updatePolicyCache(policy).getId()))); - } - - return cache; + public List findByResource(String resourceId, String resourceServerId) { + return cacheResult(new StringBuilder("findByResource").append(resourceServerId).append(resourceId).toString(), () -> getDelegate().findByResource(resourceId, resourceServerId)); } @Override public List findByResourceType(String resourceType, String resourceServerId) { - List cache = new ArrayList<>(); - - for (Entry entry : this.cache.entrySet()) { - String cacheKey = (String) entry.getKey(); - - if (cacheKey.startsWith(POLICY_ID_CACHE_PREFIX)) { - List value = (List) entry.getValue(); - CachedPolicy policy = value.get(0); - - if (policy.getResourceServerId().equals(resourceServerId) && policy.getConfig().getOrDefault("defaultResourceType", "").equals(resourceType)) { - cache.add(findById(policy.getId())); - } - } - } - - if (cache.isEmpty()) { - getDelegate().findByResourceType(resourceType, resourceServerId).forEach(policy -> cache.add(findById(updatePolicyCache(policy).getId()))); - } - - return cache; + return cacheResult(new StringBuilder("findByResourceType").append(resourceServerId).append(resourceType).toString(), () -> getDelegate().findByResourceType(resourceType, resourceServerId)); } @Override public List findByScopeIds(List scopeIds, String resourceServerId) { - List cache = new ArrayList<>(); + List policies = new ArrayList<>(); - for (Entry entry : this.cache.entrySet()) { - String cacheKey = (String) entry.getKey(); - - if (cacheKey.startsWith(POLICY_ID_CACHE_PREFIX)) { - List value = (List) entry.getValue(); - CachedPolicy policy = value.get(0); - - for (String scopeId : policy.getScopesIds()) { - if (scopeIds.contains(scopeId)) { - cache.add(findById(policy.getId())); - break; - } - } - } + for (String scopeId : scopeIds) { + policies.addAll(cacheResult(new StringBuilder("findByScopeIds").append(resourceServerId).append(scopeId).toString(), () -> getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId))); } - if (cache.isEmpty()) { - getDelegate().findByScopeIds(scopeIds, resourceServerId).forEach(policy -> cache.add(findById(updatePolicyCache(policy).getId()))); - } - - return cache; + return policies; } @Override - public List findByType(String type) { - return getDelegate().findByType(type).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList()); + public List findByType(String type, String resourceServerId) { + return cacheResult(new StringBuilder("findByType").append(resourceServerId).append(type).toString(), () -> getDelegate().findByType(type, resourceServerId)); } @Override - public List findDependentPolicies(String id) { - return getDelegate().findDependentPolicies(id).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList()); + public List findDependentPolicies(String id, String resourceServerId) { + return getDelegate().findDependentPolicies(id, resourceServerId); + } + + @Override + public void notifyChange(Object cached) { + String resourceServerId; + + if (Resource.class.isInstance(cached)) { + resourceServerId = ((Resource) cached).getResourceServer().getId(); + } else if (Scope.class.isInstance(cached)){ + resourceServerId = ((Scope) cached).getResourceServer().getId(); + } else { + throw new RuntimeException("Unexpected notification [" + cached + "]"); + } + + invalidateCache(resourceServerId); } private String getCacheKeyForPolicy(String policyId) { @@ -198,10 +180,6 @@ public class CachedPolicyStore implements PolicyStore { } private StoreFactory getStoreFactory() { - if (this.storeFactory == null) { - this.storeFactory = this.session.getProvider(StoreFactory.class); - } - return this.storeFactory; } @@ -216,6 +194,9 @@ public class CachedPolicyStore implements PolicyStore { private Policy createAdapter(CachedPolicy cached) { return new Policy() { + private Set scopes; + private Set resources; + private Set associatedPolicies; private Policy updated; @Override @@ -285,54 +266,56 @@ public class CachedPolicyStore implements PolicyStore { @Override public ResourceServer getResourceServer() { - return getStoreFactory().getResourceServerStore().findById(cached.getResourceServerId()); + return getCachedStoreFactory().getResourceServerStore().findById(cached.getResourceServerId()); } @Override public void addScope(Scope scope) { - getDelegateForUpdate().addScope(getStoreFactory().getScopeStore().findById(scope.getId())); + getDelegateForUpdate().addScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())); cached.addScope(scope); } @Override public void removeScope(Scope scope) { - getDelegateForUpdate().removeScope(getStoreFactory().getScopeStore().findById(scope.getId())); + getDelegateForUpdate().removeScope(scope); cached.removeScope(scope); } @Override public void addAssociatedPolicy(Policy associatedPolicy) { - getDelegateForUpdate().addAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId())); + getDelegateForUpdate().addAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId(), cached.getResourceServerId())); cached.addAssociatedPolicy(associatedPolicy); } @Override public void removeAssociatedPolicy(Policy associatedPolicy) { - getDelegateForUpdate().removeAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId())); + getDelegateForUpdate().removeAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId(), cached.getResourceServerId())); cached.removeAssociatedPolicy(associatedPolicy); } @Override public void addResource(Resource resource) { - getDelegateForUpdate().addResource(getStoreFactory().getResourceStore().findById(resource.getId())); + getDelegateForUpdate().addResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId())); cached.addResource(resource); } @Override public void removeResource(Resource resource) { - getDelegateForUpdate().removeResource(getStoreFactory().getResourceStore().findById(resource.getId())); + getDelegateForUpdate().removeResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId())); cached.removeResource(resource); } @Override public Set getAssociatedPolicies() { - Set associatedPolicies = new HashSet<>(); + if (associatedPolicies == null) { + associatedPolicies = new HashSet<>(); - for (String id : cached.getAssociatedPoliciesIds()) { - Policy cached = findById(id); + for (String id : cached.getAssociatedPoliciesIds()) { + Policy policy = findById(id, cached.getResourceServerId()); - if (cached != null) { - associatedPolicies.add(cached); + if (policy != null) { + associatedPolicies.add(policy); + } } } @@ -341,13 +324,15 @@ public class CachedPolicyStore implements PolicyStore { @Override public Set getResources() { - Set resources = new HashSet<>(); + if (resources == null) { + resources = new HashSet<>(); - for (String id : cached.getResourcesIds()) { - Resource cached = getStoreFactory().getResourceStore().findById(id); + for (String id : cached.getResourcesIds()) { + Resource resource = getCachedStoreFactory().getResourceStore().findById(id, cached.getResourceServerId()); - if (cached != null) { - resources.add(cached); + if (resource != null) { + resources.add(resource); + } } } @@ -356,13 +341,15 @@ public class CachedPolicyStore implements PolicyStore { @Override public Set getScopes() { - Set scopes = new HashSet<>(); + if (scopes == null) { + scopes = new HashSet<>(); - for (String id : cached.getScopesIds()) { - Scope cached = getStoreFactory().getScopeStore().findById(id); + for (String id : cached.getScopesIds()) { + Scope scope = getCachedStoreFactory().getScopeStore().findById(id, cached.getResourceServerId()); - if (cached != null) { - scopes.add(cached); + if (scope != null) { + scopes.add(scope); + } } } @@ -392,9 +379,12 @@ public class CachedPolicyStore implements PolicyStore { private Policy getDelegateForUpdate() { if (this.updated == null) { - this.updated = getDelegate().findById(getId()); + this.updated = getDelegate().findById(getId(), cached.getResourceServerId()); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> cache.remove(getCacheKeyForPolicy(getId()))); + transaction.whenCommit(() -> { + cache.remove(getCacheKeyForPolicy(getId())); + invalidateCache(cached.getResourceServerId()); + }); } return this.updated; @@ -402,9 +392,16 @@ public class CachedPolicyStore implements PolicyStore { }; } + private CachedStoreFactoryProvider getCachedStoreFactory() { + if (cachedStoreFactory == null) { + cachedStoreFactory = session.getProvider(CachedStoreFactoryProvider.class); + } + return cachedStoreFactory; + } + private CachedPolicy updatePolicyCache(Policy policy) { CachedPolicy cached = new CachedPolicy(policy); - List cache = new ArrayList<>(); + List cache = new ArrayList<>(); cache.add(cached); @@ -413,4 +410,30 @@ public class CachedPolicyStore implements PolicyStore { return cached; } + private void invalidateCache(String resourceServerId) { + cacheKeys.forEach(cacheKey -> cache.keySet().stream().filter(key -> key.startsWith(cacheKey + resourceServerId)).forEach(cache::remove)); + } + + private List cacheResult(String key, Supplier> provider) { + List cached = cache.computeIfAbsent(key, (Function>) o -> { + List result = provider.get(); + + if (result.isEmpty()) { + return null; + } + + return result.stream().map(policy -> new CachedPolicy(policy)).collect(Collectors.toList()); + }); + + if (cached == null) { + return Collections.emptyList(); + } + + return cached.stream().map(new Function() { + @Override + public Policy apply(CachedPolicy cachedPolicy) { + return findById(cachedPolicy.getId(), cachedPolicy.getResourceServerId()); + } + }).collect(Collectors.toList()); + } } \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java index 2685135784..ed98500706 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java @@ -18,6 +18,10 @@ package org.keycloak.models.authorization.infinispan; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.infinispan.Cache; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; @@ -28,16 +32,13 @@ import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvid import org.keycloak.models.authorization.infinispan.entities.CachedResourceServer; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - /** * @author Pedro Igor */ public class CachedResourceServerStore implements ResourceServerStore { private static final String RS_ID_CACHE_PREFIX = "rs-id-"; + private static final String RS_CLIENT_ID_CACHE_PREFIX = "rs-client-id-"; private final KeycloakSession session; private final CacheTransaction transaction; @@ -45,11 +46,12 @@ public class CachedResourceServerStore implements ResourceServerStore { private ResourceServerStore delegate; private final Cache cache; - public CachedResourceServerStore(KeycloakSession session, CacheTransaction transaction) { + public CachedResourceServerStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { this.session = session; this.transaction = transaction; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); + this.storeFactory = storeFactory; } @Override @@ -64,7 +66,14 @@ public class CachedResourceServerStore implements ResourceServerStore { @Override public void delete(String id) { getDelegate().delete(id); - this.transaction.whenCommit(() -> this.cache.remove(getCacheKeyForResourceServer(id))); + this.transaction.whenCommit(() -> { + List servers = cache.remove(getCacheKeyForResourceServer(id)); + + if (servers != null) { + CachedResourceServer entry = servers.get(0); + cache.remove(getCacheKeyForResourceServerClientId(entry.getClientId())); + } + }); } @Override @@ -87,32 +96,31 @@ public class CachedResourceServerStore implements ResourceServerStore { @Override public ResourceServer findByClient(String id) { - for (Map.Entry entry : this.cache.entrySet()) { - String cacheKey = (String) entry.getKey(); + String cacheKeyForResourceServer = getCacheKeyForResourceServerClientId(id); + List cached = this.cache.get(cacheKeyForResourceServer); - if (cacheKey.startsWith(RS_ID_CACHE_PREFIX)) { - List cache = (List) entry.getValue(); - ResourceServer resourceServer = cache.get(0); + if (cached == null) { + ResourceServer resourceServer = getDelegate().findByClient(id); - if (resourceServer.getClientId().equals(id)) { - return findById(resourceServer.getId()); - } + if (resourceServer != null) { + cache.put(cacheKeyForResourceServer, Arrays.asList(resourceServer.getId())); + return findById(resourceServer.getId()); } + + return null; } - ResourceServer resourceServer = getDelegate().findByClient(id); - - if (resourceServer != null) { - return findById(updateResourceServerCache(resourceServer).getId()); - } - - return null; + return findById(cached.get(0)); } private String getCacheKeyForResourceServer(String id) { return RS_ID_CACHE_PREFIX + id; } + private String getCacheKeyForResourceServerClientId(String id) { + return RS_CLIENT_ID_CACHE_PREFIX + id; + } + private ResourceServerStore getDelegate() { if (this.delegate == null) { this.delegate = getStoreFactory().getResourceServerStore(); @@ -122,10 +130,6 @@ public class CachedResourceServerStore implements ResourceServerStore { } private StoreFactory getStoreFactory() { - if (this.storeFactory == null) { - this.storeFactory = session.getProvider(StoreFactory.class); - } - return this.storeFactory; } private ResourceServer createAdapter(ResourceServer cached) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java index 8696d8d11e..742f2e4f41 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java @@ -18,6 +18,16 @@ package org.keycloak.models.authorization.infinispan; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.infinispan.Cache; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; @@ -28,14 +38,7 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedResource; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; +import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; /** * @author Pedro Igor @@ -43,60 +46,63 @@ import java.util.stream.Collectors; public class CachedResourceStore implements ResourceStore { private static final String RESOURCE_ID_CACHE_PREFIX = "rsc-id-"; - private static final String RESOURCE_OWNER_CACHE_PREFIX = "rsc-owner-"; + private static final String RESOURCE_NAME_CACHE_PREFIX = "rsc-name-"; private final KeycloakSession session; private final CacheTransaction transaction; + private final List cacheKeys; private StoreFactory storeFactory; private ResourceStore delegate; - private final Cache cache; + private final Cache> cache; - public CachedResourceStore(KeycloakSession session, CacheTransaction transaction) { + public CachedResourceStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { this.session = session; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); this.transaction = transaction; + cacheKeys = new ArrayList<>(); + cacheKeys.add("findByOwner"); + this.storeFactory = storeFactory; } @Override public Resource create(String name, ResourceServer resourceServer, String owner) { Resource resource = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); - this.transaction.whenRollback(() -> cache.remove(getCacheKeyForResource(resource.getId()))); + this.transaction.whenRollback(() -> { + cache.remove(getCacheKeyForResource(resource.getId())); + invalidateCache(resourceServer.getId()); + }); return createAdapter(new CachedResource(resource)); } @Override public void delete(String id) { - List removed = this.cache.remove(getCacheKeyForResource(id)); - - if (removed != null) { - CachedResource cachedResource = removed.get(0); - List byOwner = this.cache.get(getResourceOwnerCacheKey(cachedResource.getOwner())); - - if (byOwner != null) { - byOwner.remove(id); - - if (byOwner.isEmpty()) { - this.cache.remove(getResourceOwnerCacheKey(cachedResource.getOwner())); - } - } - } - + Resource resource = findById(id, null); + ResourceServer resourceServer = resource.getResourceServer(); getDelegate().delete(id); + this.transaction.whenCommit(() -> { + List resources = cache.remove(getCacheKeyForResource(id)); + + if (resources != null) { + CachedResource entry = resources.get(0); + cache.remove(getCacheKeyForResourceName(entry.getName(), entry.getResourceServerId())); + } + + invalidateCache(resourceServer.getId()); + }); } @Override - public Resource findById(String id) { + public Resource findById(String id, String resourceServerId) { String cacheKeyForResource = getCacheKeyForResource(id); List cached = this.cache.get(cacheKeyForResource); if (cached == null) { - Resource resource = getDelegate().findById(id); + Resource resource = getDelegate().findById(id, resourceServerId); if (resource != null) { - updateCachedIds(getResourceOwnerCacheKey(resource.getOwner()), resource, false); return createAdapter(updateResourceCache(resource)); } @@ -107,20 +113,13 @@ public class CachedResourceStore implements ResourceStore { } @Override - public List findByOwner(String ownerId) { - - for (Resource resource : getDelegate().findByOwner(ownerId)) { - updateCachedIds(getResourceOwnerCacheKey(ownerId), resource, true); - } - - return ((List) this.cache.getOrDefault(getResourceOwnerCacheKey(ownerId), Collections.emptyList())).stream().map(this::findById) - .filter(resource -> resource != null) - .collect(Collectors.toList()); + public List findByOwner(String ownerId, String resourceServerId) { + return cacheResult(new StringBuilder("findByOwner").append(resourceServerId).append(ownerId).toString(), () -> getDelegate().findByOwner(ownerId, resourceServerId)); } @Override public List findByResourceServer(String resourceServerId) { - return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); + return getDelegate().findByResourceServer(resourceServerId); } @Override @@ -129,43 +128,42 @@ public class CachedResourceStore implements ResourceStore { } @Override - public List findByScope(String... id) { - return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); + public List findByScope(List id, String resourceServerId) { + return getDelegate().findByScope(id, resourceServerId); } @Override public Resource findByName(String name, String resourceServerId) { - for (Entry entry : this.cache.entrySet()) { - String cacheKey = (String) entry.getKey(); + String cacheKeyForResource = getCacheKeyForResourceName(name, resourceServerId); + List cached = this.cache.get(cacheKeyForResource); - if (cacheKey.startsWith(RESOURCE_ID_CACHE_PREFIX)) { - List value = (List) entry.getValue(); - CachedResource resource = value.get(0); + if (cached == null) { + Resource resource = getDelegate().findByName(name, resourceServerId); - if (resource.getResourceServerId().equals(resourceServerId) && resource.getName().equals(name)) { - return findById(resource.getId()); - } + if (resource != null) { + cache.put(cacheKeyForResource, Arrays.asList(new CachedResource(resource))); + return findById(resource.getId(), resourceServerId); } + + return null; } - Resource resource = getDelegate().findByName(name, resourceServerId); - - if (resource != null) { - return findById(updateResourceCache(resource).getId()); - } - - return null; + return createAdapter(cached.get(0)); } @Override - public List findByType(String type) { - return getDelegate().findByType(type).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); + public List findByType(String type, String resourceServerId) { + return getDelegate().findByType(type, resourceServerId); } private String getCacheKeyForResource(String id) { return RESOURCE_ID_CACHE_PREFIX + id; } + private String getCacheKeyForResourceName(String name, String resourceServerId) { + return RESOURCE_NAME_CACHE_PREFIX + name + "-" + resourceServerId; + } + private ResourceStore getDelegate() { if (this.delegate == null) { this.delegate = getStoreFactory().getResourceStore(); @@ -175,10 +173,6 @@ public class CachedResourceStore implements ResourceStore { } private StoreFactory getStoreFactory() { - if (this.storeFactory == null) { - this.storeFactory = session.getProvider(StoreFactory.class); - } - return this.storeFactory; } @@ -228,13 +222,15 @@ public class CachedResourceStore implements ResourceStore { @Override public List getScopes() { - List scopes = new ArrayList<>(); + if (scopes == null) { + scopes = new ArrayList<>(); - for (String id : cached.getScopesIds()) { - Scope cached = getStoreFactory().getScopeStore().findById(id); + for (String id : cached.getScopesIds()) { + Scope scope = getCachedStoreFactory().getScopeStore().findById(id, cached.getResourceServerId()); - if (cached != null) { - scopes.add(cached); + if (scope != null) { + scopes.add(scope); + } } } @@ -254,7 +250,7 @@ public class CachedResourceStore implements ResourceStore { @Override public ResourceServer getResourceServer() { - return getStoreFactory().getResourceServerStore().findById(cached.getResourceServerId()); + return getCachedStoreFactory().getResourceServerStore().findById(cached.getResourceServerId()); } @Override @@ -264,15 +260,19 @@ public class CachedResourceStore implements ResourceStore { @Override public void updateScopes(Set scopes) { - getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getStoreFactory().getScopeStore().findById(scope.getId())).collect(Collectors.toSet())); + getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); cached.updateScopes(scopes); } private Resource getDelegateForUpdate() { if (this.updated == null) { - this.updated = getDelegate().findById(getId()); + this.updated = getDelegate().findById(getId(), cached.getResourceServerId()); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> cache.remove(getCacheKeyForResource(getId()))); + transaction.whenRollback(() -> { + cache.remove(getCacheKeyForResource(cached.getId())); + invalidateCache(cached.getResourceServerId()); + getCachedStoreFactory().getPolicyStore().notifyChange(cached); + }); } return this.updated; @@ -280,6 +280,10 @@ public class CachedResourceStore implements ResourceStore { }; } + private CachedStoreFactoryProvider getCachedStoreFactory() { + return session.getProvider(CachedStoreFactoryProvider.class); + } + private CachedResource updateResourceCache(Resource resource) { CachedResource cached = new CachedResource(resource); List cache = new ArrayList<>(); @@ -291,23 +295,30 @@ public class CachedResourceStore implements ResourceStore { return cached; } - private void updateCachedIds(String cacheKey, Resource resource, boolean create) { - List cached = this.cache.get(cacheKey); + private List cacheResult(String key, Supplier> provider) { + List cached = cache.computeIfAbsent(key, (Function>) o -> { + List result = provider.get(); + + if (result.isEmpty()) { + return null; + } + + return result.stream().map(resource -> new CachedResource(resource)).collect(Collectors.toList()); + }); if (cached == null) { - if (!create) { - return; - } - cached = new ArrayList<>(); - this.cache.put(cacheKey, cached); + return Collections.emptyList(); } - if (cached != null && !cached.contains(resource.getId())) { - cached.add(resource.getId()); - } + return cached.stream().map(new Function() { + @Override + public Resource apply(CachedResource cached) { + return findById(cached.getId(), cached.getResourceServerId()); + } + }).collect(Collectors.toList()); } - private String getResourceOwnerCacheKey(String ownerId) { - return RESOURCE_OWNER_CACHE_PREFIX + ownerId; + private void invalidateCache(String resourceServerId) { + cacheKeys.forEach(cacheKey -> cache.keySet().stream().filter(key -> key.startsWith(cacheKey + resourceServerId)).forEach(cache::remove)); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java index f86a7d1bb8..77a6970936 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java @@ -18,6 +18,11 @@ package org.keycloak.models.authorization.infinispan; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + import org.infinispan.Cache; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; @@ -27,11 +32,7 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedScope; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; +import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; /** * @author Pedro Igor @@ -39,6 +40,7 @@ import java.util.Map.Entry; public class CachedScopeStore implements ScopeStore { private static final String SCOPE_ID_CACHE_PREFIX = "scp-id-"; + private static final String SCOPE_NAME_CACHE_PREFIX = "scp-name-"; private final Cache cache; private final KeycloakSession session; @@ -46,11 +48,12 @@ public class CachedScopeStore implements ScopeStore { private ScopeStore delegate; private StoreFactory storeFactory; - public CachedScopeStore(KeycloakSession session, CacheTransaction transaction) { + public CachedScopeStore(KeycloakSession session, CacheTransaction transaction, StoreFactory storeFactory) { this.session = session; this.transaction = transaction; InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); + this.storeFactory = storeFactory; } @Override @@ -65,16 +68,23 @@ public class CachedScopeStore implements ScopeStore { @Override public void delete(String id) { getDelegate().delete(id); - this.transaction.whenCommit(() -> cache.remove(getCacheKeyForScope(id))); + this.transaction.whenCommit(() -> { + List scopes = cache.remove(getCacheKeyForScope(id)); + + if (scopes != null) { + CachedScope entry = scopes.get(0); + cache.remove(getCacheKeyForScopeName(entry.getName(), entry.getResourceServerId())); + } + }); } @Override - public Scope findById(String id) { + public Scope findById(String id, String resourceServerId) { String cacheKeyForScope = getCacheKeyForScope(id); List cached = this.cache.get(cacheKeyForScope); if (cached == null) { - Scope scope = getDelegate().findById(id); + Scope scope = getDelegate().findById(id, resourceServerId); if (scope != null) { return createAdapter(updateScopeCache(scope)); @@ -88,26 +98,21 @@ public class CachedScopeStore implements ScopeStore { @Override public Scope findByName(String name, String resourceServerId) { - for (Entry entry : this.cache.entrySet()) { - String cacheKey = (String) entry.getKey(); + String cacheKeyForScope = getCacheKeyForScopeName(name, resourceServerId); + List cached = this.cache.get(cacheKeyForScope); - if (cacheKey.startsWith(SCOPE_ID_CACHE_PREFIX)) { - List cache = (List) entry.getValue(); - CachedScope scope = cache.get(0); + if (cached == null) { + Scope scope = getDelegate().findByName(name, resourceServerId); - if (scope.getResourceServerId().equals(resourceServerId) && scope.getName().equals(name)) { - return findById(scope.getId()); - } + if (scope != null) { + cache.put(cacheKeyForScope, Arrays.asList(scope.getId())); + return findById(scope.getId(), resourceServerId); } + + return null; } - Scope scope = getDelegate().findByName(name, resourceServerId); - - if (scope != null) { - return findById(updateScopeCache(scope).getId()); - } - - return null; + return findById(cached.get(0), resourceServerId); } @Override @@ -124,6 +129,10 @@ public class CachedScopeStore implements ScopeStore { return SCOPE_ID_CACHE_PREFIX + id; } + private String getCacheKeyForScopeName(String name, String resourceServerId) { + return SCOPE_NAME_CACHE_PREFIX + name + "-" + resourceServerId; + } + private ScopeStore getDelegate() { if (this.delegate == null) { this.delegate = getStoreFactory().getScopeStore(); @@ -133,10 +142,6 @@ public class CachedScopeStore implements ScopeStore { } private StoreFactory getStoreFactory() { - if (this.storeFactory == null) { - this.storeFactory = session.getProvider(StoreFactory.class); - } - return this.storeFactory; } @@ -174,14 +179,17 @@ public class CachedScopeStore implements ScopeStore { @Override public ResourceServer getResourceServer() { - return getStoreFactory().getResourceServerStore().findById(cached.getResourceServerId()); + return getCachedStoreFactory().getResourceServerStore().findById(cached.getResourceServerId()); } private Scope getDelegateForUpdate() { if (this.updated == null) { - this.updated = getDelegate().findById(getId()); + this.updated = getDelegate().findById(getId(), cached.getResourceServerId()); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> cache.remove(getCacheKeyForScope(getId()))); + transaction.whenCommit(() -> { + cache.remove(getCacheKeyForScope(getId())); + getCachedStoreFactory().getPolicyStore().notifyChange(updated); + }); } return this.updated; @@ -189,6 +197,10 @@ public class CachedScopeStore implements ScopeStore { }; } + private CachedStoreFactoryProvider getCachedStoreFactory() { + return session.getProvider(CachedStoreFactoryProvider.class); + } + private CachedScope updateScopeCache(Scope scope) { CachedScope cached = new CachedScope(scope); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java index 56a385fe12..ff66da6fac 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java @@ -22,6 +22,7 @@ import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; +import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; @@ -36,31 +37,41 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide private final KeycloakSession session; private final CacheTransaction transaction; + private final StoreFactory storeFactory; + private final CachedResourceStore resourceStore; + private final CachedScopeStore scopeStore; + private final CachedPolicyStore policyStore; + private ResourceServerStore resourceServerStore; InfinispanStoreFactoryProvider(KeycloakSession delegate) { this.session = delegate; this.transaction = new CacheTransaction(); this.session.getTransactionManager().enlistAfterCompletion(transaction); + storeFactory = this.session.getProvider(StoreFactory.class); + resourceStore = new CachedResourceStore(this.session, this.transaction, storeFactory); + resourceServerStore = new CachedResourceServerStore(this.session, this.transaction, storeFactory); + scopeStore = new CachedScopeStore(this.session, this.transaction, storeFactory); + policyStore = new CachedPolicyStore(this.session, this.transaction, storeFactory); } @Override public ResourceStore getResourceStore() { - return new CachedResourceStore(this.session, this.transaction); + return resourceStore; } @Override public ResourceServerStore getResourceServerStore() { - return new CachedResourceServerStore(this.session, this.transaction); + return resourceServerStore; } @Override public ScopeStore getScopeStore() { - return new CachedScopeStore(this.session, this.transaction); + return scopeStore; } @Override public PolicyStore getPolicyStore() { - return new CachedPolicyStore(this.session, this.transaction); + return policyStore; } @Override diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java index b57cd1efd5..544018da0a 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java @@ -19,8 +19,10 @@ package org.keycloak.authorization.jpa.store; import org.keycloak.authorization.jpa.entities.PolicyEntity; import org.keycloak.authorization.jpa.entities.ResourceServerEntity; +import org.keycloak.authorization.jpa.entities.ScopeEntity; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.models.utils.KeycloakModelUtils; @@ -68,17 +70,30 @@ public class JPAPolicyStore implements PolicyStore { @Override public void delete(String id) { - Policy policy = findById(id); + Policy policy = entityManager.find(PolicyEntity.class, id); if (policy != null) { - getEntityManager().remove(policy); + this.entityManager.remove(policy); } } @Override - public Policy findById(String id) { - return getEntityManager().find(PolicyEntity.class, id); + public Policy findById(String id, String resourceServerId) { + if (id == null) { + return null; + } + + if (resourceServerId == null) { + return entityManager.find(PolicyEntity.class, id); + } + + Query query = entityManager.createQuery("from PolicyEntity where resourceServer.id = :serverId and id = :id"); + + query.setParameter("serverId", resourceServerId); + query.setParameter("id", id); + + return entityManager.find(PolicyEntity.class, id); } @Override @@ -142,32 +157,23 @@ public class JPAPolicyStore implements PolicyStore { } @Override - public List findByResource(final String resourceId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where r.id = :resourceId"); + public List findByResource(final String resourceId, String resourceServerId) { + Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"); query.setParameter("resourceId", resourceId); + query.setParameter("serverId", resourceServerId); return query.getResultList(); } @Override public List findByResourceType(final String resourceType, String resourceServerId) { - List policies = new ArrayList<>(); - Query query = getEntityManager().createQuery("from PolicyEntity where resourceServer.id = :serverId"); + Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.config c where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c = :type"); query.setParameter("serverId", resourceServerId); + query.setParameter("type", resourceType); - List models = query.getResultList(); - - for (Policy policy : models) { - String defaultType = policy.getConfig().get("defaultResourceType"); - - if (defaultType != null && defaultType.equals(resourceType) && policy.getResources().isEmpty()) { - policies.add(policy); - } - } - - return policies; + return query.getResultList(); } @Override @@ -177,7 +183,7 @@ public class JPAPolicyStore implements PolicyStore { } // Use separate subquery to handle DB2 and MSSSQL - Query query = getEntityManager().createQuery("select pe from PolicyEntity pe where pe.id IN (select p.id from PolicyEntity p inner join p.scopes s where p.resourceServer.id = :serverId and s.id in (:scopeIds) and p.resources is empty group by p.id) order by pe.name"); + Query query = getEntityManager().createQuery("select pe from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds)))"); query.setParameter("serverId", resourceServerId); query.setParameter("scopeIds", scopeIds); @@ -186,19 +192,21 @@ public class JPAPolicyStore implements PolicyStore { } @Override - public List findByType(String type) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p where p.type = :type"); + public List findByType(String type, String resourceServerId) { + Query query = getEntityManager().createQuery("select p from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"); + query.setParameter("serverId", resourceServerId); query.setParameter("type", type); return query.getResultList(); } @Override - public List findDependentPolicies(String policyId) { - Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where ap.id in (:policyId)"); + public List findDependentPolicies(String policyId, String resourceServerId) { + Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"); - query.setParameter("policyId", Arrays.asList(policyId)); + query.setParameter("serverId", resourceServerId); + query.setParameter("policyId", policyId); return query.getResultList(); } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java index 6d00bb66d4..9f5346da50 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java @@ -66,7 +66,7 @@ public class JPAResourceStore implements ResourceStore { @Override public void delete(String id) { - Resource resource = findById(id); + Resource resource = entityManager.find(ResourceEntity.class, id); resource.getScopes().clear(); @@ -76,19 +76,29 @@ public class JPAResourceStore implements ResourceStore { } @Override - public Resource findById(String id) { + public Resource findById(String id, String resourceServerId) { if (id == null) { return null; } + if (resourceServerId == null) { + return entityManager.find(ResourceEntity.class, id); + } + + Query query = entityManager.createQuery("from ResourceEntity where resourceServer.id = :serverId and id = :id"); + + query.setParameter("serverId", resourceServerId); + query.setParameter("id", id); + return entityManager.find(ResourceEntity.class, id); } @Override - public List findByOwner(String ownerId) { - Query query = entityManager.createQuery("from ResourceEntity where owner = :ownerId"); + public List findByOwner(String ownerId, String resourceServerId) { + Query query = entityManager.createQuery("from ResourceEntity where resourceServer.id = :serverId and owner = :ownerId"); query.setParameter("ownerId", ownerId); + query.setParameter("serverId", resourceServerId); return query.getResultList(); } @@ -112,7 +122,9 @@ public class JPAResourceStore implements ResourceStore { predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); attributes.forEach((name, value) -> { - if ("scope".equals(name)) { + if ("id".equals(name)) { + predicates.add(root.get(name).in(value)); + } else if ("scope".equals(name)) { predicates.add(root.join("scopes").get("id").in(value)); } else { predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); @@ -134,10 +146,11 @@ public class JPAResourceStore implements ResourceStore { } @Override - public List findByScope(String... id) { - Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)"); + public List findByScope(List id, String resourceServerId) { + Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"); - query.setParameter("scopeIds", Arrays.asList(id)); + query.setParameter("scopeIds", id); + query.setParameter("serverId", resourceServerId); return query.getResultList(); } @@ -159,10 +172,11 @@ public class JPAResourceStore implements ResourceStore { } @Override - public List findByType(String type) { - Query query = entityManager.createQuery("from ResourceEntity where type = :type"); + public List findByType(String type, String resourceServerId) { + Query query = entityManager.createQuery("from ResourceEntity r where r.resourceServer.id = :serverId and type = :type"); query.setParameter("type", type); + query.setParameter("serverId", resourceServerId); return query.getResultList(); } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java index d468314374..30869d3d72 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java @@ -17,12 +17,9 @@ */ package org.keycloak.authorization.jpa.store; -import org.keycloak.authorization.jpa.entities.ResourceServerEntity; -import org.keycloak.authorization.jpa.entities.ScopeEntity; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.ScopeStore; -import org.keycloak.models.utils.KeycloakModelUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.NoResultException; @@ -31,9 +28,13 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; + +import org.keycloak.authorization.jpa.entities.ResourceServerEntity; +import org.keycloak.authorization.jpa.entities.ScopeEntity; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; +import org.keycloak.authorization.store.ScopeStore; +import org.keycloak.models.utils.KeycloakModelUtils; /** * @author Pedro Igor @@ -61,12 +62,30 @@ public class JPAScopeStore implements ScopeStore { @Override public void delete(String id) { - this.entityManager.remove(findById(id)); + Scope scope = entityManager.find(ScopeEntity.class, id); + + if (scope != null) { + this.entityManager.remove(scope); + } } @Override - public Scope findById(String id) { + public Scope findById(String id, String resourceServerId) { + if (id == null) { + return null; + } + + if (true) { + return entityManager.find(ScopeEntity.class, id); + } + + Query query = entityManager.createQuery("from ScopeEntity where resourceServer.id = :serverId and id = :id"); + + query.setParameter("serverId", resourceServerId); + query.setParameter("id", id); + return entityManager.find(ScopeEntity.class, id); + } @Override @@ -74,8 +93,8 @@ public class JPAScopeStore implements ScopeStore { try { Query query = entityManager.createQuery("select s from ScopeEntity s inner join s.resourceServer rs where rs.id = :resourceServerId and name = :name"); - query.setParameter("name", name); query.setParameter("resourceServerId", resourceServerId); + query.setParameter("name", name); return (Scope) query.getSingleResult(); } catch (NoResultException nre) { @@ -102,7 +121,11 @@ public class JPAScopeStore implements ScopeStore { predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); attributes.forEach((name, value) -> { - predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + if ("id".equals(name)) { + predicates.add(root.get(name).in(value)); + } else { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + } }); querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java index 2b28f16463..928bba9bba 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/PolicyAdapter.java @@ -124,21 +124,21 @@ public class PolicyAdapter extends AbstractMongoAdapter implements @Override public Set getAssociatedPolicies() { return getMongoEntity().getAssociatedPolicies().stream() - .map((Function) id -> authorizationProvider.getStoreFactory().getPolicyStore().findById(id)) + .map((Function) id -> authorizationProvider.getStoreFactory().getPolicyStore().findById(id, getMongoEntity().getResourceServerId())) .collect(Collectors.toSet()); } @Override public Set getResources() { return getMongoEntity().getResources().stream() - .map((Function) id -> authorizationProvider.getStoreFactory().getResourceStore().findById(id)) + .map((Function) id -> authorizationProvider.getStoreFactory().getResourceStore().findById(id, getMongoEntity().getResourceServerId())) .collect(Collectors.toSet()); } @Override public Set getScopes() { return getMongoEntity().getScopes().stream() - .map((Function) id -> authorizationProvider.getStoreFactory().getScopeStore().findById(id)) + .map((Function) id -> authorizationProvider.getStoreFactory().getScopeStore().findById(id, getMongoEntity().getResourceServerId())) .collect(Collectors.toSet()); } diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java index 7c67f6ea75..8138a2479d 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/adapter/ResourceAdapter.java @@ -68,7 +68,7 @@ public class ResourceAdapter extends AbstractMongoAdapter implem @Override public List getScopes() { return getMongoEntity().getScopes().stream() - .map(id -> authorizationProvider.getStoreFactory().getScopeStore().findById(id)) + .map(id -> authorizationProvider.getStoreFactory().getScopeStore().findById(id, getResourceServer().getId())) .collect(toList()); } diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java index 04a3d9ac4b..c7227f699a 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java @@ -70,7 +70,7 @@ public class MongoPolicyStore implements PolicyStore { } @Override - public Policy findById(String id) { + public Policy findById(String id, String resourceServerId) { PolicyEntity entity = getMongoStore().loadEntity(PolicyEntity.class, id, getInvocationContext()); if (entity == null) { @@ -89,7 +89,7 @@ public class MongoPolicyStore implements PolicyStore { .get(); return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())).findFirst().orElse(null); + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)).findFirst().orElse(null); } @Override @@ -99,7 +99,7 @@ public class MongoPolicyStore implements PolicyStore { .get(); return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } @@ -125,17 +125,18 @@ public class MongoPolicyStore implements PolicyStore { DBObject sort = new BasicDBObject("name", 1); return getMongoStore().loadEntities(PolicyEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() - .map(policy -> findById(policy.getId())).collect(toList()); + .map(policy -> findById(policy.getId(), resourceServerId)).collect(toList()); } @Override - public List findByResource(String resourceId) { + public List findByResource(String resourceId, String resourceServerId) { DBObject query = new QueryBuilder() + .and("resourceServerId").is(resourceServerId) .and("resources").is(resourceId) .get(); return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } @@ -150,7 +151,7 @@ public class MongoPolicyStore implements PolicyStore { String defaultResourceType = policyEntity.getConfig().get("defaultResourceType"); return defaultResourceType != null && defaultResourceType.equals(resourceType); }) - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } @@ -162,29 +163,31 @@ public class MongoPolicyStore implements PolicyStore { .get(); return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } @Override - public List findByType(String type) { + public List findByType(String type, String resourceServerId) { DBObject query = new QueryBuilder() + .and("resourceServerId").is(resourceServerId) .and("type").is(type) .get(); return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } @Override - public List findDependentPolicies(String policyId) { + public List findDependentPolicies(String policyId, String resourceServerId) { DBObject query = new QueryBuilder() + .and("resourceServerId").is(resourceServerId) .and("associatedPolicies").is(policyId) .get(); return getMongoStore().loadEntities(PolicyEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java index a85de72970..79f6a9ec71 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java @@ -70,7 +70,7 @@ public class MongoResourceStore implements ResourceStore { } @Override - public Resource findById(String id) { + public Resource findById(String id, String resourceServerId) { ResourceEntity entity = getMongoStore().loadEntity(ResourceEntity.class, id, getInvocationContext()); if (entity == null) { @@ -81,13 +81,14 @@ public class MongoResourceStore implements ResourceStore { } @Override - public List findByOwner(String ownerId) { + public List findByOwner(String ownerId, String resourceServerId) { DBObject query = new QueryBuilder() + .and("resourceServerId").is(resourceServerId) .and("owner").is(ownerId) .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() - .map(scope -> findById(scope.getId())).collect(toList()); + .map(scope -> findById(scope.getId(), resourceServerId)).collect(toList()); } @Override @@ -97,7 +98,7 @@ public class MongoResourceStore implements ResourceStore { .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() - .map(scope -> findById(scope.getId())).collect(toList()); + .map(scope -> findById(scope.getId(), resourceServerId)).collect(toList()); } @Override @@ -116,39 +117,41 @@ public class MongoResourceStore implements ResourceStore { DBObject sort = new BasicDBObject("name", 1); return getMongoStore().loadEntities(ResourceEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() - .map(scope -> findById(scope.getId())).collect(toList()); + .map(scope -> findById(scope.getId(), resourceServerId)).collect(toList()); } @Override - public List findByScope(String... id) { + public List findByScope(List id, String resourceServerId) { DBObject query = new QueryBuilder() + .and("resourceServerId").is(resourceServerId) .and("scopes").in(id) .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } @Override public Resource findByName(String name, String resourceServerId) { DBObject query = new QueryBuilder() - .and("name").is(name) .and("resourceServerId").is(resourceServerId) + .and("name").is(name) .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())).findFirst().orElse(null); + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)).findFirst().orElse(null); } @Override - public List findByType(String type) { + public List findByType(String type, String resourceServerId) { DBObject query = new QueryBuilder() + .and("resourceServerId").is(resourceServerId) .and("type").is(type) .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(policyEntity -> findById(policyEntity.getId(), resourceServerId)) .collect(toList()); } diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java index 4b7edd6a2f..04decb291f 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java @@ -69,7 +69,7 @@ public class MongoScopeStore implements ScopeStore { } @Override - public Scope findById(String id) { + public Scope findById(String id, String resourceServerId) { ScopeEntity entity = getMongoStore().loadEntity(ScopeEntity.class, id, getInvocationContext()); if (entity == null) { @@ -87,7 +87,7 @@ public class MongoScopeStore implements ScopeStore { .get(); return getMongoStore().loadEntities(ScopeEntity.class, query, getInvocationContext()).stream() - .map(scope -> findById(scope.getId())).findFirst().orElse(null); + .map(scope -> findById(scope.getId(), scope.getResourceServerId())).findFirst().orElse(null); } @Override @@ -97,7 +97,7 @@ public class MongoScopeStore implements ScopeStore { .get(); return getMongoStore().loadEntities(ScopeEntity.class, query, getInvocationContext()).stream() - .map(policyEntity -> findById(policyEntity.getId())) + .map(scope -> findById(scope.getId(), scope.getResourceServerId())) .collect(toList()); } @@ -113,7 +113,7 @@ public class MongoScopeStore implements ScopeStore { DBObject sort = new BasicDBObject("name", 1); return getMongoStore().loadEntities(ScopeEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() - .map(scope -> findById(scope.getId())).collect(toList()); + .map(scope -> findById(scope.getId(), scope.getResourceServerId())).collect(toList()); } private MongoStoreInvocationContext getInvocationContext() { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java index fb7c91b796..3aafae31b5 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java @@ -18,6 +18,12 @@ package org.keycloak.authorization; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + import org.keycloak.authorization.permission.evaluator.Evaluators; import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator; import org.keycloak.authorization.policy.provider.PolicyProvider; @@ -26,11 +32,6 @@ import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; - -import java.util.List; -import java.util.concurrent.Executor; -import java.util.stream.Collectors; /** *

The main contract here is the creation of {@link org.keycloak.authorization.permission.evaluator.PermissionEvaluator} instances. Usually @@ -62,22 +63,22 @@ public final class AuthorizationProvider implements Provider { private final DefaultPolicyEvaluator policyEvaluator; private final Executor scheduller; - private final StoreFactory storeFactory; - private final List policyProviderFactories; + private final Supplier storeFactory; + private final Map policyProviderFactories; private final KeycloakSession keycloakSession; private final RealmModel realm; - public AuthorizationProvider(KeycloakSession session, RealmModel realm, StoreFactory storeFactory, Executor scheduller) { + public AuthorizationProvider(KeycloakSession session, RealmModel realm, Supplier storeFactory, Map policyProviderFactories, Executor scheduller) { this.keycloakSession = session; this.realm = realm; this.storeFactory = storeFactory; this.scheduller = scheduller; - this.policyProviderFactories = configurePolicyProviderFactories(session); - this.policyEvaluator = new DefaultPolicyEvaluator(this, this.policyProviderFactories); + this.policyProviderFactories = policyProviderFactories; + this.policyEvaluator = new DefaultPolicyEvaluator(this); } - public AuthorizationProvider(KeycloakSession session, RealmModel realm, StoreFactory storeFactory) { - this(session, realm, storeFactory, Runnable::run); + public AuthorizationProvider(KeycloakSession session, RealmModel realm, StoreFactory storeFactory, Map policyProviderFactories) { + this(session, realm, () -> storeFactory, policyProviderFactories, Runnable::run); } /** @@ -87,7 +88,7 @@ public final class AuthorizationProvider implements Provider { * @return a {@link Evaluators} instance */ public Evaluators evaluators() { - return new Evaluators(this.policyProviderFactories, this.policyEvaluator, this.scheduller); + return new Evaluators(this.policyEvaluator, this.scheduller); } /** @@ -96,7 +97,7 @@ public final class AuthorizationProvider implements Provider { * @return the {@link StoreFactory} */ public StoreFactory getStoreFactory() { - return this.storeFactory; + return this.storeFactory.get(); } /** @@ -104,8 +105,8 @@ public final class AuthorizationProvider implements Provider { * * @return a {@link List} containing all registered {@link PolicyProviderFactory} */ - public List getProviderFactories() { - return this.policyProviderFactories; + public Collection getProviderFactories() { + return this.policyProviderFactories.values(); } /** @@ -116,7 +117,24 @@ public final class AuthorizationProvider implements Provider { * @return a {@link PolicyProviderFactory} with the given type */ public F getProviderFactory(String type) { - return (F) getProviderFactories().stream().filter(policyProviderFactory -> policyProviderFactory.getId().equals(type)).findFirst().orElse(null); + return (F) policyProviderFactories.get(type); + } + + /** + * Returns a {@link PolicyProviderFactory} given a type. + * + * @param type the type of the policy provider + * @param the expected type of the provider + * @return a {@link PolicyProvider} with the given type + */ + public

P getProvider(String type) { + PolicyProviderFactory policyProviderFactory = policyProviderFactories.get(type); + + if (policyProviderFactory == null) { + return null; + } + + return (P) policyProviderFactory.create(this); } public KeycloakSession getKeycloakSession() { @@ -127,16 +145,6 @@ public final class AuthorizationProvider implements Provider { return realm; } - private List configurePolicyProviderFactories(KeycloakSession session) { - List providerFactories = session.getKeycloakSessionFactory().getProviderFactories(PolicyProvider.class); - - if (providerFactories.isEmpty()) { - throw new RuntimeException("Could not find any policy provider."); - } - - return providerFactories.stream().map(providerFactory -> (PolicyProviderFactory) providerFactory).collect(Collectors.toList()); - } - @Override public void close() { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java index e26ad1c87e..ed1aa89172 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java @@ -18,13 +18,12 @@ package org.keycloak.authorization.permission.evaluator; +import java.util.List; +import java.util.concurrent.Executor; + import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator; import org.keycloak.authorization.policy.evaluation.EvaluationContext; -import org.keycloak.authorization.policy.provider.PolicyProviderFactory; - -import java.util.List; -import java.util.concurrent.Executor; /** * A factory for the different {@link PermissionEvaluator} implementations. @@ -33,12 +32,10 @@ import java.util.concurrent.Executor; */ public final class Evaluators { - private final List policyProviderFactories; private final DefaultPolicyEvaluator policyEvaluator; private final Executor scheduler; - public Evaluators(List policyProviderFactories, DefaultPolicyEvaluator policyEvaluator, Executor scheduler) { - this.policyProviderFactories = policyProviderFactories; + public Evaluators(DefaultPolicyEvaluator policyEvaluator, Executor scheduler) { this.policyEvaluator = policyEvaluator; this.scheduler = scheduler; } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java index 13e08e4ebc..463050746d 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/ScheduledPermissionEvaluator.java @@ -17,11 +17,10 @@ */ package org.keycloak.authorization.permission.evaluator; -import org.keycloak.authorization.Decision; - -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import org.keycloak.authorization.Decision; + /** * @author Pedro Igor * @see PermissionEvaluator @@ -38,6 +37,6 @@ class ScheduledPermissionEvaluator implements PermissionEvaluator { @Override public void evaluate(Decision decision) { - CompletableFuture.runAsync(() -> publisher.evaluate(decision), scheduler); + publisher.evaluate(decision); } } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java index abd3f935ce..286636057a 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java @@ -37,8 +37,10 @@ public abstract class DecisionResultCollector implements DecisionPedro Igor */ public class DefaultPolicyEvaluator implements PolicyEvaluator { private final AuthorizationProvider authorization; - private Map policyProviders = new HashMap<>(); + private final StoreFactory storeFactory; + private final PolicyStore policyStore; - public DefaultPolicyEvaluator(AuthorizationProvider authorization, List policyProviderFactories) { + public DefaultPolicyEvaluator(AuthorizationProvider authorization) { this.authorization = authorization; - - for (PolicyProviderFactory providerFactory : policyProviderFactories) { - this.policyProviders.put(providerFactory.getId(), providerFactory); - } + storeFactory = this.authorization.getStoreFactory(); + policyStore = storeFactory.getPolicyStore(); } @Override public void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision) { ResourceServer resourceServer = permission.getResourceServer(); + PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode(); - if (PolicyEnforcementMode.DISABLED.equals(resourceServer.getPolicyEnforcementMode())) { + if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) { createEvaluation(permission, executionContext, decision, null, null).grant(); return; } - StoreFactory storeFactory = this.authorization.getStoreFactory(); - PolicyStore policyStore = storeFactory.getPolicyStore(); - AtomicInteger policiesCount = new AtomicInteger(0); - Consumer consumer = createDecisionConsumer(permission, executionContext, decision, policiesCount); + AtomicBoolean verified = new AtomicBoolean(false); + Consumer consumer = createDecisionConsumer(permission, executionContext, decision, verified); Resource resource = permission.getResource(); + List scopes = permission.getScopes(); if (resource != null) { - List resourcePolicies = policyStore.findByResource(resource.getId()); - - if (!resourcePolicies.isEmpty()) { - resourcePolicies.forEach(consumer); - } + evaluatePolicies(() -> policyStore.findByResource(resource.getId(), resourceServer.getId()), consumer); if (resource.getType() != null) { - policyStore.findByResourceType(resource.getType(), resourceServer.getId()).forEach(consumer); + evaluatePolicies(() -> policyStore.findByResourceType(resource.getType(), resourceServer.getId()), consumer); } - if (permission.getScopes().isEmpty() && !resource.getScopes().isEmpty()) { - policyStore.findByScopeIds(resource.getScopes().stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()).forEach(consumer); + if (scopes.isEmpty() && !resource.getScopes().isEmpty()) { + scopes.removeAll(resource.getScopes()); + evaluatePolicies(() -> policyStore.findByScopeIds(resource.getScopes().stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()), consumer); } } - if (!permission.getScopes().isEmpty()) { - policyStore.findByScopeIds(permission.getScopes().stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()).forEach(consumer); + if (!scopes.isEmpty()) { + evaluatePolicies(() -> policyStore.findByScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()), consumer); } - if (PolicyEnforcementMode.PERMISSIVE.equals(resourceServer.getPolicyEnforcementMode()) && policiesCount.get() == 0) { + if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && verified.get()) { createEvaluation(permission, executionContext, decision, null, null).grant(); } } - private Consumer createDecisionConsumer(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AtomicInteger policiesCount) { + private void evaluatePolicies(Supplier> supplier, Consumer consumer) { + List policies = supplier.get(); + + if (!policies.isEmpty()) { + policies.forEach(consumer); + } + } + + private Consumer createDecisionConsumer(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AtomicBoolean verified) { return (parentPolicy) -> { - if (hasRequestedScopes(permission, parentPolicy)) { - for (Policy associatedPolicy : parentPolicy.getAssociatedPolicies()) { - PolicyProviderFactory providerFactory = policyProviders.get(associatedPolicy.getType()); - - if (providerFactory == null) { - throw new RuntimeException("Could not find a policy provider for policy type [" + associatedPolicy.getType() + "]."); - } - - PolicyProvider policyProvider = providerFactory.create(associatedPolicy, this.authorization); - - if (policyProvider == null) { - throw new RuntimeException("Unknown parentPolicy provider for type [" + associatedPolicy.getType() + "]."); - } - - DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy, associatedPolicy); - - policyProvider.evaluate(evaluation); - evaluation.denyIfNoEffect(); - - policiesCount.incrementAndGet(); - } + if (!hasRequestedScopes(permission, parentPolicy)) { + return; } + + for (Policy associatedPolicy : parentPolicy.getAssociatedPolicies()) { + PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType()); + + if (policyProvider == null) { + throw new RuntimeException("Unknown parentPolicy provider for type [" + associatedPolicy.getType() + "]."); + } + + DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy, associatedPolicy); + + policyProvider.evaluate(evaluation); + evaluation.denyIfNoEffect(); + } + + verified.compareAndSet(false, true); }; } private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy, Policy associatedPolicy) { - return new DefaultEvaluation(permission, executionContext, parentPolicy, associatedPolicy, decision); + return new DefaultEvaluation(permission, executionContext, parentPolicy, associatedPolicy, decision, authorization); } private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) { @@ -136,7 +134,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator { Set policyResources = policy.getResources(); if (resourcePermission != null && !policyResources.isEmpty()) { - if (!policyResources.stream().filter(resource -> resource.getId().equals(resourcePermission.getId())).findFirst().isPresent()) { + if (!policyResources.stream().filter(resource -> resource.getId().equals(resourcePermission.getId())).findFirst().isPresent()) { return false; } } @@ -161,7 +159,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator { String type = resource.getType(); if (type != null) { - List resourcesByType = authorization.getStoreFactory().getResourceStore().findByType(type); + List resourcesByType = authorization.getStoreFactory().getResourceStore().findByType(type, resource.getResourceServer().getId()); for (Resource resourceType : resourcesByType) { if (resourceType.getOwner().equals(resource.getResourceServer().getClientId())) { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java index f5b08682b7..4ac0264b34 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Evaluation.java @@ -18,6 +18,8 @@ package org.keycloak.authorization.policy.evaluation; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.permission.ResourcePermission; /** @@ -42,6 +44,15 @@ public interface Evaluation { */ EvaluationContext getContext(); + /** + * Returns the {@link Policy}. being evaluated. + * + * @return the evaluation context + */ + Policy getPolicy(); + + AuthorizationProvider getAuthorizationProvider(); + /** * Grants the requested permission to the caller. */ diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java index f82bdb7573..f7041b58ae 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/provider/PolicyProviderFactory.java @@ -32,7 +32,7 @@ public interface PolicyProviderFactory extends ProviderFactory { String getGroup(); - PolicyProvider create(Policy policy, AuthorizationProvider authorization); + PolicyProvider create(AuthorizationProvider authorization); PolicyProviderAdminService getAdminResource(ResourceServer resourceServer); } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java index 51c7dd71ec..626e31704b 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java @@ -53,9 +53,10 @@ public interface PolicyStore { * Returns a {@link Policy} with the given id * * @param id the identifier of the policy + * @param resourceServerId the resource server id * @return a policy with the given identifier. */ - Policy findById(String id); + Policy findById(String id, String resourceServerId); /** * Returns a {@link Policy} with the given name @@ -87,9 +88,10 @@ public interface PolicyStore { * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given resourceId. * * @param resourceId the identifier of a resource + * @param resourceServerId the resource server id * @return a list of policies associated with the given resource */ - List findByResource(String resourceId); + List findByResource(String resourceId, String resourceServerId); /** * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given type. @@ -113,15 +115,26 @@ public interface PolicyStore { * Returns a list of {@link Policy} with the given type. * * @param type the type of the policy + * @param resourceServerId the resource server id * @return a list of policies with the given type */ - List findByType(String type); + List findByType(String type, String resourceServerId); /** * Returns a list of {@link Policy} that depends on another policy with the given id. * * @param id the id of the policy to query its dependents + * @param resourceServerId the resource server id * @return a list of policies that depends on the a policy with the given identifier */ - List findDependentPolicies(String id); + List findDependentPolicies(String id, String resourceServerId); + + /** + * Notify this store about changes to data associated with policies. E.g.: resources and scopes.. + * + * TODO: need a better strategy to handle cross-references between stores, specially in cases where the store is caching data. Use some event-based solution here. + * + * @param cached + */ + default void notifyChange(Object cached) {} } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java index f06be7853f..e897664669 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceStore.java @@ -53,7 +53,7 @@ public interface ResourceStore { * @param id the identifier of an existing resource instance * @return the resource instance with the given identifier or null if no instance was found */ - Resource findById(String id); + Resource findById(String id, String resourceServerId); /** * Finds all {@link Resource} instances with the given {@code ownerId}. @@ -61,7 +61,7 @@ public interface ResourceStore { * @param ownerId the identifier of the owner * @return a list with all resource instances owned by the given owner */ - List findByOwner(String ownerId); + List findByOwner(String ownerId, String resourceServerId); /** * Finds all {@link Resource} instances associated with a given resource server. @@ -86,7 +86,7 @@ public interface ResourceStore { * @param id one or more scope identifiers * @return a list of resources associated with the given scope(s) */ - List findByScope(String... id); + List findByScope(List id, String resourceServerId); /** * Find a {@link Resource} by its name. @@ -103,5 +103,5 @@ public interface ResourceStore { * @param type the type of the resource * @return a list of resources with the given type */ - List findByType(String type); + List findByType(String type, String resourceServerId); } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java index 81a7064fc7..fa9e70d9af 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/ScopeStore.java @@ -53,17 +53,17 @@ public interface ScopeStore { * Returns a {@link Scope} with the given id * * @param id the identifier of the scope - * + * @param resourceServerId the resource server id * @return a scope with the given identifier. */ - Scope findById(String id); + Scope findById(String id, String resourceServerId); /** * Returns a {@link Scope} with the given name * * @param name the name of the scope * - * @param resourceServerId + * @param resourceServerId the resource server id * @return a scope with the given name. */ Scope findByName(String name, String resourceServerId); diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java index 4f0ef32cd6..ed24c53d50 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/RealmSynchronizer.java @@ -36,7 +36,7 @@ public class RealmSynchronizer implements Synchronizer { StoreFactory storeFactory = authorizationProvider.getStoreFactory(); event.getRealm().getClients().forEach(clientModel -> { - ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getClientId()); + ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getId()); if (resourceServer != null) { String id = resourceServer.getId(); diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java index 01830ff07a..03a2cda718 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/syncronization/UserSynchronizer.java @@ -17,11 +17,16 @@ package org.keycloak.authorization.store.syncronization; +import java.util.function.Consumer; + import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.UserRemovedEvent; import org.keycloak.provider.ProviderFactory; @@ -39,17 +44,25 @@ public class UserSynchronizer implements Synchronizer { UserModel userModel = event.getUser(); ResourceStore resourceStore = storeFactory.getResourceStore(); PolicyStore policyStore = storeFactory.getPolicyStore(); + ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore(); + RealmModel realm = event.getRealm(); - resourceStore.findByOwner(userModel.getId()).forEach(resource -> { - String resourceId = resource.getId(); - policyStore.findByResource(resourceId).forEach(policy -> { - if (policy.getResources().size() == 1) { - policyStore.delete(policy.getId()); - } else { - policy.removeResource(resource); - } - }); - resourceStore.delete(resourceId); + realm.getClients().forEach(clientModel -> { + ResourceServer resourceServer = resourceServerStore.findByClient(clientModel.getId()); + + if (resourceServer != null) { + resourceStore.findByOwner(userModel.getId(), resourceServer.getId()).forEach(resource -> { + String resourceId = resource.getId(); + policyStore.findByResource(resourceId, resourceServer.getId()).forEach(policy -> { + if (policy.getResources().size() == 1) { + policyStore.delete(policy.getId()); + } else { + policy.removeResource(resource); + } + }); + resourceStore.delete(resourceId); + }); + } }); } } diff --git a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java index 995dafb3fb..f8844f15bb 100644 --- a/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java +++ b/server-spi-private/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java @@ -70,7 +70,7 @@ public class MigrateTo2_1_0 implements Migration { ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getId()); if (resourceServer != null) { - policyStore.findByType("role").forEach(policy -> { + policyStore.findByType("role", resourceServer.getId()).forEach(policy -> { Map config = policy.getConfig(); String roles = config.get("roles"); List roleConfig; diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 27dd6dc64f..b03f8e6055 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -17,14 +17,23 @@ package org.keycloak.models.utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.authorization.store.StoreFactory; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; @@ -84,20 +93,6 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.storage.StorageId; -import org.keycloak.util.JsonSerialization; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; /** * @author Bill Burke @@ -770,34 +765,16 @@ public class ModelToRepresentation { } public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider) { + return toRepresentation(model, authorizationProvider, true); + } + + public static ScopeRepresentation toRepresentation(Scope model, AuthorizationProvider authorizationProvider, boolean deep) { ScopeRepresentation scope = new ScopeRepresentation(); scope.setId(model.getId()); scope.setName(model.getName()); scope.setIconUri(model.getIconUri()); - StoreFactory storeFactory = authorizationProvider.getStoreFactory(); - - scope.setResources(new ArrayList<>()); - - storeFactory.getResourceStore().findByScope(model.getId()).forEach(resource -> scope.getResources().add(toRepresentation(resource, resource.getResourceServer(), authorizationProvider))); - - PolicyStore policyStore = storeFactory.getPolicyStore(); - - scope.setPolicies(new ArrayList<>()); - - policyStore.findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()).forEach(policyModel -> { - PolicyRepresentation policy = new PolicyRepresentation(); - - policy.setId(policyModel.getId()); - policy.setName(policyModel.getName()); - policy.setType(policyModel.getType()); - - if (!scope.getPolicies().contains(policy)) { - scope.getPolicies().add(policy); - } - }); - return scope; } @@ -813,7 +790,7 @@ public class ModelToRepresentation { return server; } - public static PolicyRepresentation toRepresentation(Policy model, AuthorizationProvider authorization) { + public static PolicyRepresentation toRepresentation(Policy model) { PolicyRepresentation representation = new PolicyRepresentation(); representation.setId(model.getId()); @@ -822,45 +799,16 @@ public class ModelToRepresentation { representation.setType(model.getType()); representation.setDecisionStrategy(model.getDecisionStrategy()); representation.setLogic(model.getLogic()); - representation.setConfig(new HashMap<>(model.getConfig())); - - List policies = authorization.getStoreFactory().getPolicyStore().findDependentPolicies(model.getId()); - - representation.setDependentPolicies(policies.stream().map(policy -> { - PolicyRepresentation representation1 = new PolicyRepresentation(); - - representation1.setId(policy.getId()); - representation1.setName(policy.getName()); - - return representation1; - }).collect(Collectors.toList())); - - List associatedPolicies = new ArrayList<>(); - - List obj = model.getAssociatedPolicies().stream().map(policy -> { - PolicyRepresentation representation1 = new PolicyRepresentation(); - - representation1.setId(policy.getId()); - representation1.setName(policy.getName()); - representation1.setType(policy.getType()); - - associatedPolicies.add(representation1); - - return policy.getId(); - }).collect(Collectors.toList()); - - representation.setAssociatedPolicies(associatedPolicies); - - try { - representation.getConfig().put("applyPolicies", JsonSerialization.writeValueAsString(obj)); - } catch (IOException e) { - e.printStackTrace(); - } + representation.setConfig(model.getConfig()); return representation; } public static ResourceRepresentation toRepresentation(Resource model, ResourceServer resourceServer, AuthorizationProvider authorization) { + return toRepresentation(model, resourceServer, authorization, true); + } + + public static ResourceRepresentation toRepresentation(Resource model, ResourceServer resourceServer, AuthorizationProvider authorization, Boolean deep) { ResourceRepresentation resource = new ResourceRepresentation(); resource.setId(model.getId()); @@ -891,55 +839,36 @@ public class ModelToRepresentation { resource.setOwner(owner); - resource.setScopes(model.getScopes().stream().map(model1 -> { - ScopeRepresentation scope = new ScopeRepresentation(); - scope.setId(model1.getId()); - scope.setName(model1.getName()); - String iconUri = model1.getIconUri(); - if (iconUri != null) { - scope.setIconUri(iconUri); - } - return scope; - }).collect(Collectors.toSet())); - - resource.setTypedScopes(new ArrayList<>()); - - if (resource.getType() != null) { - ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); - for (Resource typed : resourceStore.findByType(resource.getType())) { - if (typed.getOwner().equals(resourceServer.getClientId()) && !typed.getId().equals(resource.getId())) { - resource.setTypedScopes(typed.getScopes().stream().map(model1 -> { - ScopeRepresentation scope = new ScopeRepresentation(); - scope.setId(model1.getId()); - scope.setName(model1.getName()); - String iconUri = model1.getIconUri(); - if (iconUri != null) { - scope.setIconUri(iconUri); - } - return scope; - }).filter(scopeRepresentation -> !resource.getScopes().contains(scopeRepresentation)).collect(Collectors.toList())); + if (deep) { + resource.setScopes(model.getScopes().stream().map(model1 -> { + ScopeRepresentation scope = new ScopeRepresentation(); + scope.setId(model1.getId()); + scope.setName(model1.getName()); + String iconUri = model1.getIconUri(); + if (iconUri != null) { + scope.setIconUri(iconUri); } - } - } + return scope; + }).collect(Collectors.toSet())); - resource.setPolicies(new ArrayList<>()); + resource.setTypedScopes(new ArrayList<>()); - Set policies = new HashSet<>(); - PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore(); - - policies.addAll(policyStore.findByResource(resource.getId())); - policies.addAll(policyStore.findByResourceType(resource.getType(), resourceServer.getId())); - policies.addAll(policyStore.findByScopeIds(resource.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), resourceServer.getId())); - - for (Policy policyModel : policies) { - PolicyRepresentation policy = new PolicyRepresentation(); - - policy.setId(policyModel.getId()); - policy.setName(policyModel.getName()); - policy.setType(policyModel.getType()); - - if (!resource.getPolicies().contains(policy)) { - resource.getPolicies().add(policy); + if (resource.getType() != null) { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + for (Resource typed : resourceStore.findByType(resource.getType(), resourceServer.getId())) { + if (typed.getOwner().equals(resourceServer.getClientId()) && !typed.getId().equals(resource.getId())) { + resource.setTypedScopes(typed.getScopes().stream().map(model1 -> { + ScopeRepresentation scope = new ScopeRepresentation(); + scope.setId(model1.getId()); + scope.setName(model1.getName()); + String iconUri = model1.getIconUri(); + if (iconUri != null) { + scope.setIconUri(iconUri); + } + return scope; + }).filter(scopeRepresentation -> !resource.getScopes().contains(scopeRepresentation)).collect(Collectors.toList())); + } + } } } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index d8b934fef9..6b33ffb9d0 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1894,7 +1894,7 @@ public class RepresentationToModel { if (roles != null && !roles.isEmpty()) { try { - List rolesMap = JsonSerialization.readValue(roles, List.class); + List rolesMap = (List)JsonSerialization.readValue(roles, List.class); config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> { String roleName = roleConfig.get("id").toString(); String clientId = null; @@ -1946,7 +1946,7 @@ public class RepresentationToModel { if (users != null && !users.isEmpty()) { try { - List usersMap = JsonSerialization.readValue(users, List.class); + List usersMap = (List) JsonSerialization.readValue(users, List.class); config.put("users", JsonSerialization.writeValueAsString(usersMap.stream().map(userId -> { UserModel user = session.users().getUserByUsername(userId, realm); @@ -1970,12 +1970,12 @@ public class RepresentationToModel { if (scopes != null && !scopes.isEmpty()) { try { ScopeStore scopeStore = storeFactory.getScopeStore(); - List scopesMap = JsonSerialization.readValue(scopes, List.class); + List scopesMap = (List) JsonSerialization.readValue(scopes, List.class); config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> { Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId()); if (newScope == null) { - newScope = scopeStore.findById(scopeName); + newScope = scopeStore.findById(scopeName, resourceServer.getId()); } if (newScope == null) { @@ -1995,21 +1995,18 @@ public class RepresentationToModel { ResourceStore resourceStore = storeFactory.getResourceStore(); try { List resources = JsonSerialization.readValue(policyResources, List.class); - config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(new Function() { - @Override - public String apply(String resourceName) { - Resource resource = resourceStore.findByName(resourceName, resourceServer.getId()); + config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> { + Resource resource = resourceStore.findByName(resourceName, resourceServer.getId()); - if (resource == null) { - resource = resourceStore.findById(resourceName); - } - - if (resource == null) { - throw new RuntimeException("Resource with name [" + resourceName + "] not defined."); - } - - return resource.getId(); + if (resource == null) { + resource = resourceStore.findById(resourceName, resourceServer.getId()); } + + if (resource == null) { + throw new RuntimeException("Resource with name [" + resourceName + "] not defined."); + } + + return resource.getId(); }).collect(Collectors.toList()))); } catch (Exception e) { throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e); @@ -2021,12 +2018,12 @@ public class RepresentationToModel { if (applyPolicies != null && !applyPolicies.isEmpty()) { PolicyStore policyStore = storeFactory.getPolicyStore(); try { - List policies = JsonSerialization.readValue(applyPolicies, List.class); + List policies = (List) JsonSerialization.readValue(applyPolicies, List.class); config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> { Policy policy = policyStore.findByName(policyName, resourceServer.getId()); if (policy == null) { - policy = policyStore.findById(policyName); + policy = policyStore.findById(policyName, resourceServer.getId()); } if (policy == null) { @@ -2058,7 +2055,7 @@ public class RepresentationToModel { Policy existing; if (policy.getId() != null) { - existing = policyStore.findById(policy.getId()); + existing = policyStore.findById(policy.getId(), resourceServer.getId()); } else { existing = policyStore.findByName(policy.getName(), resourceServer.getId()); } @@ -2115,7 +2112,14 @@ public class RepresentationToModel { } } if (!hasScope) { - policy.addScope(storeFactory.getScopeStore().findById(scopeId)); + ResourceServer resourceServer = policy.getResourceServer(); + Scope scope = storeFactory.getScopeStore().findById(scopeId, resourceServer.getId()); + + if (scope == null) { + storeFactory.getScopeStore().findByName(scopeId, resourceServer.getId()); + } + + policy.addScope(scope); } } @@ -2131,6 +2135,8 @@ public class RepresentationToModel { policy.removeScope(scopeModel); } } + + policy.getConfig().remove("scopes"); } } @@ -2160,7 +2166,7 @@ public class RepresentationToModel { if (!hasPolicy) { - Policy associatedPolicy = policyStore.findById(policyId); + Policy associatedPolicy = policyStore.findById(policyId, resourceServer.getId()); if (associatedPolicy == null) { associatedPolicy = policyStore.findByName(policyId, resourceServer.getId()); @@ -2182,6 +2188,8 @@ public class RepresentationToModel { policy.removeAssociatedPolicy(policyModel);; } } + + policy.getConfig().remove("applyPolicies"); } } @@ -2206,7 +2214,7 @@ public class RepresentationToModel { } } if (!hasResource && !"".equals(resourceId)) { - policy.addResource(storeFactory.getResourceStore().findById(resourceId)); + policy.addResource(storeFactory.getResourceStore().findById(resourceId, policy.getResourceServer().getId())); } } @@ -2223,6 +2231,8 @@ public class RepresentationToModel { policy.removeResource(resourceModel); } } + + policy.getConfig().remove("resources"); } } @@ -2231,7 +2241,7 @@ public class RepresentationToModel { Resource existing; if (resource.getId() != null) { - existing = resourceStore.findById(resource.getId()); + existing = resourceStore.findById(resource.getId(), resourceServer.getId()); } else { existing = resourceStore.findByName(resource.getName(), resourceServer.getId()); } @@ -2282,7 +2292,7 @@ public class RepresentationToModel { Scope existing; if (scope.getId() != null) { - existing = scopeStore.findById(scope.getId()); + existing = scopeStore.findById(scope.getId(), resourceServer.getId()); } else { existing = scopeStore.findByName(scope.getName(), resourceServer.getId()); } diff --git a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java index 6146594f4e..159e5aa81f 100644 --- a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java +++ b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java @@ -18,14 +18,20 @@ package org.keycloak.authorization; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + import org.keycloak.Config; +import org.keycloak.authorization.policy.provider.PolicyProvider; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; - -import java.util.concurrent.Executor; +import org.keycloak.provider.ProviderFactory; /** * @author Pedro Igor @@ -33,6 +39,7 @@ import java.util.concurrent.Executor; public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory { private Executor scheduler; + private Map policyProviderFactories; @Override public AuthorizationProvider create(KeycloakSession session) { @@ -54,6 +61,7 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide @Override public void postInit(KeycloakSessionFactory factory) { + policyProviderFactories = configurePolicyProviderFactories(factory); } @Override @@ -74,6 +82,21 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide storeFactory = session.getProvider(StoreFactory.class); } - return new AuthorizationProvider(session, realm, storeFactory); + return new AuthorizationProvider(session, realm, storeFactory, policyProviderFactories); } + + private Map configurePolicyProviderFactories(KeycloakSessionFactory keycloakSessionFactory) { + List providerFactories = keycloakSessionFactory.getProviderFactories(PolicyProvider.class); + + if (providerFactories.isEmpty()) { + throw new RuntimeException("Could not find any policy provider."); + } + + HashMap providers = new HashMap<>(); + + providerFactories.forEach(providerFactory -> providers.put(providerFactory.getId(), (PolicyProviderFactory) providerFactory)); + + return providers; + } + } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java index 076f9af628..b096d24d4d 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java @@ -17,6 +17,27 @@ */ package org.keycloak.authorization.admin; +import static java.util.Arrays.asList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Produces; +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.Suspended; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; + import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.admin.representation.PolicyEvaluationRequest; @@ -46,29 +67,10 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.Urls; import org.keycloak.services.resources.admin.RealmAuth; -import javax.ws.rs.Consumes; -import javax.ws.rs.POST; -import javax.ws.rs.Produces; -import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.container.Suspended; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.Arrays.asList; - /** * @author Pedro Igor */ @@ -144,40 +146,35 @@ public class PolicyEvaluationService { private List createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) { List resources = representation.getResources(); return resources.stream().flatMap((Function>) resource -> { - Set givenScopes = resource.getScopes(); + StoreFactory storeFactory = authorization.getStoreFactory(); + if (resource == null) { + resource = new PolicyEvaluationRequest.Resource(); + } + + Set givenScopes = resource.getScopes(); if (givenScopes == null) { givenScopes = new HashSet(); } - StoreFactory storeFactory = authorization.getStoreFactory(); + Set scopeNames = givenScopes.stream().map(ScopeRepresentation::getName).collect(Collectors.toSet()); if (resource.getId() != null) { - Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId()); - return Permissions.createResourcePermissions(resourceModel, givenScopes, authorization).stream(); + Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId(), resourceServer.getId()); + return Permissions.createResourcePermissions(resourceModel, scopeNames, authorization).stream(); } else if (resource.getType() != null) { - Set finalGivenScopes = givenScopes; - return storeFactory.getResourceStore().findByType(resource.getType()).stream().flatMap(resource1 -> Permissions.createResourcePermissions(resource1, finalGivenScopes, authorization).stream()); + return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().flatMap(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization).stream()); } else { ScopeStore scopeStore = storeFactory.getScopeStore(); - List scopes = givenScopes.stream().map(scopeName -> scopeStore.findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList()); - List collect = scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList()); + List scopes = scopeNames.stream().map(scopeName -> scopeStore.findByName(scopeName, this.resourceServer.getId())).collect(Collectors.toList()); + List collect = new ArrayList(); - if (scopes.isEmpty()) { - scopes = scopeStore.findByResourceServer(resourceServer.getId()); + if (!scopes.isEmpty()) { + collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList())); + } else { + collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization)); } - for (Scope scope : scopes) { - collect.addAll(storeFactory.getResourceStore().findByScope(scope.getId()).stream().map(resource12 -> new ResourcePermission(resource12, asList(scope), resourceServer)).collect(Collectors.toList())); - } - - collect.addAll(storeFactory.getResourceStore().findByResourceServer(resourceServer.getId()).stream().map(new Function() { - @Override - public ResourcePermission apply(Resource resource) { - return new ResourcePermission(resource, resource.getScopes(), resourceServer); - } - }).collect(Collectors.toList())); - return collect.stream(); } }).collect(Collectors.toList()); diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index e274bea07f..3caf2ea886 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -17,21 +17,15 @@ */ package org.keycloak.authorization.admin; -import org.jboss.resteasy.annotations.cache.NoCache; -import org.jboss.resteasy.spi.ResteasyProviderFactory; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; -import org.keycloak.authorization.policy.provider.PolicyProviderFactory; -import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.models.Constants; -import org.keycloak.models.utils.ModelToRepresentation; -import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; -import org.keycloak.representations.idm.authorization.PolicyRepresentation; -import org.keycloak.representations.idm.authorization.ResourceRepresentation; -import org.keycloak.services.resources.admin.RealmAuth; +import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; +import static org.keycloak.models.utils.RepresentationToModel.toModel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -44,15 +38,24 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; -import static org.keycloak.models.utils.RepresentationToModel.toModel; +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.ResourceStore; +import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.Constants; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.services.resources.admin.RealmAuth; /** * @author Pedro Igor @@ -100,7 +103,7 @@ public class PolicyService { this.auth.requireManage(); representation.setId(id); StoreFactory storeFactory = authorization.getStoreFactory(); - Policy policy = storeFactory.getPolicyStore().findById(representation.getId()); + Policy policy = storeFactory.getPolicyStore().findById(representation.getId(), resourceServer.getId()); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -127,7 +130,7 @@ public class PolicyService { this.auth.requireManage(); StoreFactory storeFactory = authorization.getStoreFactory(); PolicyStore policyStore = storeFactory.getPolicyStore(); - Policy policy = policyStore.findById(id); + Policy policy = policyStore.findById(id, resourceServer.getId()); if (policy == null) { return Response.status(Status.NOT_FOUND).build(); @@ -143,7 +146,7 @@ public class PolicyService { } } - policyStore.findDependentPolicies(id).forEach(dependentPolicy -> { + policyStore.findDependentPolicies(id, resourceServer.getId()).forEach(dependentPolicy -> { if (dependentPolicy.getAssociatedPolicies().size() == 1) { policyStore.delete(dependentPolicy.getId()); } else { @@ -163,13 +166,109 @@ public class PolicyService { public Response findById(@PathParam("id") String id) { this.auth.requireView(); StoreFactory storeFactory = authorization.getStoreFactory(); - Policy model = storeFactory.getPolicyStore().findById(id); + Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); if (model == null) { return Response.status(Status.NOT_FOUND).build(); } - return Response.ok(toRepresentation(model, authorization)).build(); + return Response.ok(toRepresentation(model)).build(); + } + + @Path("{id}/dependentPolicies") + @GET + @Produces("application/json") + @NoCache + public Response getDependentPolicies(@PathParam("id") String id) { + this.auth.requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + List policies = authorization.getStoreFactory().getPolicyStore().findDependentPolicies(model.getId(), resourceServer.getId()); + + return Response.ok(policies.stream().map(policy -> { + PolicyRepresentation representation1 = new PolicyRepresentation(); + + representation1.setId(policy.getId()); + representation1.setName(policy.getName()); + representation1.setType(policy.getType()); + + return representation1; + }).collect(Collectors.toList())).build(); + } + + @Path("{id}/scopes") + @GET + @Produces("application/json") + @NoCache + public Response getScopes(@PathParam("id") String id) { + this.auth.requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(model.getScopes().stream().map(scope -> { + ScopeRepresentation representation = new ScopeRepresentation(); + + representation.setId(scope.getId()); + representation.setName(scope.getName()); + + return representation; + }).collect(Collectors.toList())).build(); + } + + @Path("{id}/resources") + @GET + @Produces("application/json") + @NoCache + public Response getResources(@PathParam("id") String id) { + this.auth.requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(model.getResources().stream().map(resource -> { + ResourceRepresentation representation = new ResourceRepresentation(); + + representation.setId(resource.getId()); + representation.setName(resource.getName()); + + return representation; + }).collect(Collectors.toList())).build(); + } + + @Path("{id}/associatedPolicies") + @GET + @Produces("application/json") + @NoCache + public Response getAssociatedPolicies(@PathParam("id") String id) { + this.auth.requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + Policy model = storeFactory.getPolicyStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(model.getAssociatedPolicies().stream().map(policy -> { + PolicyRepresentation representation1 = new PolicyRepresentation(); + + representation1.setId(policy.getId()); + representation1.setName(policy.getName()); + representation1.setType(policy.getType()); + + return representation1; + }).collect(Collectors.toList())).build(); } @Path("/search") @@ -190,13 +289,14 @@ public class PolicyService { return Response.status(Status.OK).build(); } - return Response.ok(toRepresentation(model, authorization)).build(); + return Response.ok(toRepresentation(model)).build(); } @GET @Produces("application/json") @NoCache - public Response findAll(@QueryParam("name") String name, + public Response findAll(@QueryParam("policyId") String id, + @QueryParam("name") String name, @QueryParam("type") String type, @QueryParam("resource") String resource, @QueryParam("permission") Boolean permission, @@ -206,6 +306,10 @@ public class PolicyService { Map search = new HashMap<>(); + if (id != null && !"".equals(id.trim())) { + search.put("id", new String[] {id}); + } + if (name != null && !"".equals(name.trim())) { search.put("name", new String[] {name}); } @@ -216,16 +320,17 @@ public class PolicyService { StoreFactory storeFactory = authorization.getStoreFactory(); + PolicyStore policyStore = storeFactory.getPolicyStore(); if (resource != null && !"".equals(resource.trim())) { List policies = new ArrayList<>(); HashMap resourceSearch = new HashMap<>(); resourceSearch.put("name", new String[] {resource}); - storeFactory.getResourceStore().findByResourceServer(resourceSearch, resourceServer.getId(), -1, -1).forEach(resource1 -> { - ResourceRepresentation resourceRepresentation = ModelToRepresentation.toRepresentation(resource1, resourceServer, authorization); - resourceRepresentation.getPolicies().forEach(policyRepresentation -> { - Policy associated = storeFactory.getPolicyStore().findById(policyRepresentation.getId()); + ResourceStore resourceStore = storeFactory.getResourceStore(); + resourceStore.findByResourceServer(resourceSearch, resourceServer.getId(), -1, -1).forEach(resource1 -> { + policyStore.findByResource(resource1.getId(), resourceServer.getId()).forEach(policyRepresentation -> { + Policy associated = policyStore.findById(policyRepresentation.getId(), resourceServer.getId()); policies.add(associated); findAssociatedPolicies(associated, policies); }); @@ -243,8 +348,8 @@ public class PolicyService { } return Response.ok( - storeFactory.getPolicyStore().findByResourceServer(search, resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() - .map(policy -> toRepresentation(policy, authorization)) + policyStore.findByResourceServer(search, resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() + .map(policy -> toRepresentation(policy)) .collect(Collectors.toList())) .build(); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index c1cb821f33..bb241d8805 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -32,8 +32,10 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.RealmAuth; @@ -48,10 +50,15 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; + +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -127,7 +134,7 @@ public class ResourceSetService { resource.setId(id); StoreFactory storeFactory = this.authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); - Resource model = resourceStore.findById(resource.getId()); + Resource model = resourceStore.findById(resource.getId(), resourceServer.getId()); if (model == null) { return Response.status(Status.NOT_FOUND).build(); @@ -143,14 +150,14 @@ public class ResourceSetService { public Response delete(@PathParam("id") String id) { requireManage(); StoreFactory storeFactory = authorization.getStoreFactory(); - Resource resource = storeFactory.getResourceStore().findById(id); + Resource resource = storeFactory.getResourceStore().findById(id, resourceServer.getId()); if (resource == null) { return Response.status(Status.NOT_FOUND).build(); } PolicyStore policyStore = storeFactory.getPolicyStore(); - List policies = policyStore.findByResource(id); + List policies = policyStore.findByResource(id, resourceServer.getId()); for (Policy policyModel : policies) { if (policyModel.getResources().size() == 1) { @@ -172,13 +179,93 @@ public class ResourceSetService { public Response findById(@PathParam("id") String id) { requireView(); StoreFactory storeFactory = authorization.getStoreFactory(); - Resource model = storeFactory.getResourceStore().findById(id); + Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId()); if (model == null) { return Response.status(Status.NOT_FOUND).build(); } - return Response.ok(toRepresentation(model, this.resourceServer, authorization)).build(); + return Response.ok(toRepresentation(model, this.resourceServer, authorization, true)).build(); + } + + @Path("{id}/scopes") + @GET + @NoCache + @Produces("application/json") + public Response getScopes(@PathParam("id") String id) { + requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + List scopes = model.getScopes().stream().map(scope -> { + ScopeRepresentation representation = new ScopeRepresentation(); + + representation.setId(scope.getId()); + representation.setName(scope.getName()); + + return representation; + }).collect(Collectors.toList()); + + if (model.getType() != null) { + ResourceStore resourceStore = authorization.getStoreFactory().getResourceStore(); + for (Resource typed : resourceStore.findByType(model.getType(), resourceServer.getId())) { + if (typed.getOwner().equals(resourceServer.getClientId()) && !typed.getId().equals(model.getId())) { + scopes.addAll(typed.getScopes().stream().map(model1 -> { + ScopeRepresentation scope = new ScopeRepresentation(); + scope.setId(model1.getId()); + scope.setName(model1.getName()); + String iconUri = model1.getIconUri(); + if (iconUri != null) { + scope.setIconUri(iconUri); + } + return scope; + }).filter(scopeRepresentation -> !scopes.contains(scopeRepresentation)).collect(Collectors.toList())); + } + } + } + + return Response.ok(scopes).build(); + } + + @Path("{id}/permissions") + @GET + @NoCache + @Produces("application/json") + public Response getPermissions(@PathParam("id") String id) { + requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + Resource model = storeFactory.getResourceStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + PolicyStore policyStore = authorization.getStoreFactory().getPolicyStore(); + Set policies = new HashSet<>(); + + policies.addAll(policyStore.findByResource(model.getId(), resourceServer.getId())); + policies.addAll(policyStore.findByResourceType(model.getType(), resourceServer.getId())); + policies.addAll(policyStore.findByScopeIds(model.getScopes().stream().map(scope -> scope.getId()).collect(Collectors.toList()), resourceServer.getId())); + + List representation = new ArrayList<>(); + + for (Policy policyModel : policies) { + PolicyRepresentation policy = new PolicyRepresentation(); + + policy.setId(policyModel.getId()); + policy.setName(policyModel.getName()); + policy.setType(policyModel.getType()); + + if (!representation.contains(policy)) { + representation.add(policy); + } + } + + return Response.ok(representation).build(); } @Path("/search") @@ -205,18 +292,29 @@ public class ResourceSetService { @GET @NoCache @Produces("application/json") - public Response find(@QueryParam("name") String name, + public Response find(@QueryParam("_id") String id, + @QueryParam("name") String name, @QueryParam("uri") String uri, @QueryParam("owner") String owner, @QueryParam("type") String type, @QueryParam("scope") String scope, + @QueryParam("deep") Boolean deep, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResult) { requireView(); + StoreFactory storeFactory = authorization.getStoreFactory(); + if (deep == null) { + deep = true; + } + Map search = new HashMap<>(); + if (id != null && !"".equals(id.trim())) { + search.put("id", new String[] {id}); + } + if (name != null && !"".equals(name.trim())) { search.put("name", new String[] {name}); } @@ -260,9 +358,10 @@ public class ResourceSetService { search.put("scope", scopes.stream().map(Scope::getId).toArray(String[]::new)); } + Boolean finalDeep = deep; return Response.ok( storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() - .map(resource -> toRepresentation(resource, this.resourceServer, authorization)) + .map(resource -> toRepresentation(resource, resourceServer, authorization, finalDeep)) .collect(Collectors.toList())) .build(); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index 83cffebdc5..7724830a97 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -26,6 +26,8 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.models.Constants; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.RealmAuth; @@ -41,6 +43,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; + import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -85,7 +88,7 @@ public class ScopeService { this.auth.requireManage(); scope.setId(id); StoreFactory storeFactory = authorization.getStoreFactory(); - Scope model = storeFactory.getScopeStore().findById(scope.getId()); + Scope model = storeFactory.getScopeStore().findById(scope.getId(), resourceServer.getId()); if (model == null) { return Response.status(Status.NOT_FOUND).build(); @@ -101,13 +104,13 @@ public class ScopeService { public Response delete(@PathParam("id") String id) { this.auth.requireManage(); StoreFactory storeFactory = authorization.getStoreFactory(); - List resources = storeFactory.getResourceStore().findByScope(id); + List resources = storeFactory.getResourceStore().findByScope(Arrays.asList(id), resourceServer.getId()); if (!resources.isEmpty()) { return ErrorResponse.exists("Scopes can not be removed while associated with resources."); } - Scope scope = storeFactory.getScopeStore().findById(id); + Scope scope = storeFactory.getScopeStore().findById(id, resourceServer.getId()); if (scope == null) { return Response.status(Status.NOT_FOUND).build(); @@ -134,7 +137,7 @@ public class ScopeService { @Produces("application/json") public Response findById(@PathParam("id") String id) { this.auth.requireView(); - Scope model = this.authorization.getStoreFactory().getScopeStore().findById(id); + Scope model = this.authorization.getStoreFactory().getScopeStore().findById(id, resourceServer.getId()); if (model == null) { return Response.status(Status.NOT_FOUND).build(); @@ -143,6 +146,53 @@ public class ScopeService { return Response.ok(toRepresentation(model, this.authorization)).build(); } + @Path("{id}/resources") + @GET + @Produces("application/json") + public Response getResources(@PathParam("id") String id) { + this.auth.requireView(); + StoreFactory storeFactory = this.authorization.getStoreFactory(); + Scope model = storeFactory.getScopeStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + return Response.ok(storeFactory.getResourceStore().findByScope(Arrays.asList(model.getId()), resourceServer.getId()).stream().map(resource -> { + ResourceRepresentation representation = new ResourceRepresentation(); + + representation.setId(resource.getId()); + representation.setName(resource.getName()); + + return representation; + }).collect(Collectors.toList())).build(); + } + + @Path("{id}/permissions") + @GET + @Produces("application/json") + public Response getPermissions(@PathParam("id") String id) { + this.auth.requireView(); + StoreFactory storeFactory = this.authorization.getStoreFactory(); + Scope model = storeFactory.getScopeStore().findById(id, resourceServer.getId()); + + if (model == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + PolicyStore policyStore = storeFactory.getPolicyStore(); + + return Response.ok(policyStore.findByScopeIds(Arrays.asList(model.getId()), resourceServer.getId()).stream().map(policy -> { + PolicyRepresentation representation = new PolicyRepresentation(); + + representation.setId(policy.getId()); + representation.setName(policy.getName()); + representation.setType(policy.getType()); + + return representation; + }).collect(Collectors.toList())).build(); + } + @Path("/search") @GET @Produces("application/json") @@ -166,20 +216,31 @@ public class ScopeService { @GET @Produces("application/json") - public Response findAll(@QueryParam("name") String name, + public Response findAll(@QueryParam("scopeId") String id, + @QueryParam("name") String name, + @QueryParam("deep") Boolean deep, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResult) { this.auth.requireView(); + if (deep == null) { + deep = true; + } + Map search = new HashMap<>(); + if (id != null && !"".equals(id.trim())) { + search.put("id", new String[] {id}); + } + if (name != null && !"".equals(name.trim())) { search.put("name", new String[] {name}); } + Boolean finalDeep = deep; return Response.ok( this.authorization.getStoreFactory().getScopeStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : Constants.DEFAULT_MAX_RESULTS).stream() - .map(scope -> toRepresentation(scope, this.authorization)) + .map(scope -> toRepresentation(scope, this.authorization, finalDeep)) .collect(Collectors.toList())) .build(); } diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java index 17edef970f..da7b4204c2 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationRequest.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; + /** * @author Pedro Igor */ @@ -82,42 +84,7 @@ public class PolicyEvaluationRequest { this.entitlements = entitlements; } - public static class Resource { - private String id; - private String name; - private String type; - private Set scopes; + public static class Resource extends ResourceRepresentation { - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(final String type) { - this.type = type; - } - - public Set getScopes() { - return scopes; - } - - public void setScopes(final Set scopes) { - this.scopes = scopes; - } } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java index 4f0c64a8a9..83c00b8c1f 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java +++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java @@ -35,9 +35,11 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -62,7 +64,7 @@ public class PolicyEvaluationResponse { AccessToken accessToken = identity.getAccessToken(); AccessToken.Authorization authorizationData = new AccessToken.Authorization(); - authorizationData.setPermissions(Permissions.allPermits(results, authorization)); + authorizationData.setPermissions(Permissions.permits(results, authorization, resourceServer.getId())); accessToken.setAuthorization(authorizationData); response.rpt = accessToken; @@ -80,7 +82,12 @@ public class PolicyEvaluationResponse { resultsRep.add(rep); if (result.getPermission().getResource() != null) { - rep.setResource(ModelToRepresentation.toRepresentation(result.getPermission().getResource(), resourceServer, authorization)); + ResourceRepresentation resource = new ResourceRepresentation(); + + resource.setId(result.getPermission().getResource().getId()); + resource.setName(result.getPermission().getResource().getName()); + + rep.setResource(resource); } else { ResourceRepresentation resource = new ResourceRepresentation(); @@ -89,7 +96,14 @@ public class PolicyEvaluationResponse { rep.setResource(resource); } - rep.setScopes(result.getPermission().getScopes().stream().map(scope -> ModelToRepresentation.toRepresentation(scope, authorization)).collect(Collectors.toList())); + rep.setScopes(result.getPermission().getScopes().stream().map(scope -> { + ScopeRepresentation representation = new ScopeRepresentation(); + + representation.setId(scope.getId()); + representation.setName(scope.getName()); + + return representation; + }).collect(Collectors.toList())); List policies = new ArrayList<>(); @@ -100,7 +114,7 @@ public class PolicyEvaluationResponse { rep.setPolicies(policies); } - resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName())); + resultsRep.sort(Comparator.comparing(o -> o.getResource().getName())); Map groupedResults = new HashMap<>(); @@ -127,17 +141,29 @@ public class PolicyEvaluationResponse { List currentScopes = evaluationResultRepresentation.getScopes(); if (currentScopes != null) { + List allowedScopes = result.getAllowedScopes(); for (ScopeRepresentation scope : currentScopes) { if (!scopes.contains(scope)) { scopes.add(scope); } if (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT)) { - List allowedScopes = result.getAllowedScopes(); if (!allowedScopes.contains(scope)) { allowedScopes.add(scope); } + } else { + evaluationResultRepresentation.getPolicies().forEach(new Consumer() { + @Override + public void accept(PolicyResultRepresentation policyResultRepresentation) { + if (policyResultRepresentation.getStatus().equals(Effect.PERMIT)) { + if (!allowedScopes.contains(scope)) { + allowedScopes.add(scope); + } + } + } + }); } } + result.setAllowedScopes(allowedScopes); } if (resource.getId() != null) { @@ -160,18 +186,14 @@ public class PolicyEvaluationResponse { } if (policy.getStatus().equals(Effect.DENY)) { - Policy policyModel = authorization.getStoreFactory().getPolicyStore().findById(policy.getPolicy().getId()); + Policy policyModel = authorization.getStoreFactory().getPolicyStore().findById(policy.getPolicy().getId(), resourceServer.getId()); for (ScopeRepresentation scope : policyModel.getScopes().stream().map(scopeModel -> ModelToRepresentation.toRepresentation(scopeModel, authorization)).collect(Collectors.toList())) { - if (!policy.getScopes().contains(scope)) { + if (!policy.getScopes().contains(scope) && policyModel.getScopes().stream().filter(policyScope -> policyScope.getId().equals(scope.getId())).findFirst().isPresent()) { + result.getAllowedScopes().remove(scope); policy.getScopes().add(scope); } } - for (ScopeRepresentation scope : currentScopes) { - if (!policy.getScopes().contains(scope)) { - policy.getScopes().add(scope); - } - } - } + } else {} } }); @@ -183,7 +205,14 @@ public class PolicyEvaluationResponse { private static PolicyResultRepresentation toRepresentation(PolicyResult policy, AuthorizationProvider authorization) { PolicyResultRepresentation policyResultRep = new PolicyResultRepresentation(); - policyResultRep.setPolicy(ModelToRepresentation.toRepresentation(policy.getPolicy(), authorization)); + PolicyRepresentation representation = new PolicyRepresentation(); + + representation.setId(policy.getPolicy().getId()); + representation.setName(policy.getPolicy().getName()); + representation.setType(policy.getPolicy().getType()); + representation.setDecisionStrategy(policy.getPolicy().getDecisionStrategy()); + + policyResultRep.setPolicy(representation); policyResultRep.setStatus(policy.getStatus()); policyResultRep.setAssociatedPolicies(policy.getAssociatedPolicies().stream().map(result -> toRepresentation(result, authorization)).collect(Collectors.toList())); 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 77a35e9cf5..e60a0d6128 100644 --- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java +++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java @@ -110,7 +110,7 @@ public class AuthorizationTokenService { authorization.evaluators().from(createPermissions(ticket, authorizationRequest, authorization), evaluationContext).evaluate(new DecisionResultCollector() { @Override public void onComplete(List results) { - List entitlements = Permissions.allPermits(results, authorization); + List entitlements = Permissions.permits(results, authorization, ticket.getResourceServerId()); if (entitlements.isEmpty()) { HashMap error = new HashMap<>(); @@ -144,7 +144,7 @@ public class AuthorizationTokenService { Resource resource; if (requestedResource.getId() != null) { - resource = storeFactory.getResourceStore().findById(requestedResource.getId()); + resource = storeFactory.getResourceStore().findById(requestedResource.getId(), ticket.getResourceServerId()); } else { resource = storeFactory.getResourceStore().findByName(requestedResource.getName(), ticket.getResourceServerId()); } @@ -171,7 +171,7 @@ public class AuthorizationTokenService { } return scope.getId(); - }).filter(s -> s != null).collect(Collectors.toList()).toArray(new String[requestedScopes.size()]))); + }).filter(s -> s != null).collect(Collectors.toList()), ticket.getResourceServerId())); for (Resource resource1 : resources) { permissionsToEvaluate.put(resource1.getId(), collect); @@ -204,7 +204,7 @@ public class AuthorizationTokenService { if (permissions != null) { permissions.forEach(permission -> { - Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId()); + Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId(), ticket.getResourceServerId()); if (resourcePermission != null) { Set scopes = permissionsToEvaluate.get(resourcePermission.getId()); @@ -240,7 +240,7 @@ public class AuthorizationTokenService { }).collect(Collectors.toList()); return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); } else { - Resource entryResource = storeFactory.getResourceStore().findById(key); + Resource entryResource = storeFactory.getResourceStore().findById(key, resourceServer.getId()); return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); } }).collect(Collectors.toList()); 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 9f89e4a376..579417d100 100644 --- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java +++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java @@ -46,6 +46,7 @@ import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.resources.Cors; @@ -125,7 +126,7 @@ public class EntitlementService { @Override protected void onComplete(List results) { - List entitlements = Permissions.allPermits(results, authorization); + List entitlements = Permissions.allPermits(results, authorization, resourceServer); if (entitlements.isEmpty()) { HashMap error = new HashMap<>(); @@ -169,31 +170,40 @@ public class EntitlementService { StoreFactory storeFactory = authorization.getStoreFactory(); ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(client.getId()); - authorization.evaluators().from(createPermissions(entitlementRequest, resourceServer, authorization), new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate(new DecisionResultCollector() { - - @Override - public void onError(Throwable cause) { - asyncResponse.resume(cause); - } - - @Override - protected void onComplete(List results) { - List entitlements = Permissions.allPermits(results, authorization); - - if (entitlements.isEmpty()) { - HashMap error = new HashMap<>(); - - error.put(OAuth2Constants.ERROR, "not_authorized"); - - asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN) - .entity(error)) - .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()); + try { + authorization.evaluators().from(createPermissions(entitlementRequest, resourceServer, authorization), new KeycloakEvaluationContext(this.authorization.getKeycloakSession())).evaluate(new DecisionResultCollector() { + @Override + public void onError(Throwable cause) { + asyncResponse.resume(cause); } + + @Override + protected void onComplete(List results) { + List entitlements = Permissions.allPermits(results, authorization, resourceServer); + + if (entitlements.isEmpty()) { + HashMap error = new HashMap<>(); + + error.put(OAuth2Constants.ERROR, "not_authorized"); + + asyncResponse.resume(Cors.add(request, Response.status(Status.FORBIDDEN) + .entity(error)) + .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()); + } + } + }); + } catch (Exception e) { + String message = e.getMessage(); + + if (message == null) { + message = "Could not process authorization request"; } - }); + + asyncResponse.resume(ErrorResponse.error(message, Status.BAD_REQUEST)); + } } private String createRequestingPartyToken(List permissions) { @@ -215,7 +225,7 @@ public class EntitlementService { Resource resource; if (requestedResource.getResourceSetId() != null) { - resource = storeFactory.getResourceStore().findById(requestedResource.getResourceSetId()); + resource = storeFactory.getResourceStore().findById(requestedResource.getResourceSetId(), resourceServer.getId()); } else { resource = storeFactory.getResourceStore().findByName(requestedResource.getResourceSetName(), resourceServer.getId()); } @@ -242,7 +252,7 @@ public class EntitlementService { } return scope.getId(); - }).filter(s -> s != null).collect(Collectors.toList()).toArray(new String[requestedScopes.size()]))); + }).filter(s -> s != null).collect(Collectors.toList()), resourceServer.getId())); for (Resource resource1 : resources) { permissionsToEvaluate.put(resource1.getId(), collect); @@ -276,7 +286,7 @@ public class EntitlementService { if (permissions != null) { permissions.forEach(permission -> { - Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId()); + Resource resourcePermission = storeFactory.getResourceStore().findById(permission.getResourceSetId(), resourceServer.getId()); if (resourcePermission != null) { Set scopes = permissionsToEvaluate.get(resourcePermission.getId()); @@ -310,7 +320,7 @@ public class EntitlementService { }).collect(Collectors.toList()); return Arrays.asList(new ResourcePermission(null, scopes, resourceServer)).stream(); } else { - Resource entryResource = storeFactory.getResourceStore().findById(key); + Resource entryResource = storeFactory.getResourceStore().findById(key, resourceServer.getId()); return Permissions.createResourcePermissions(entryResource, entry.getValue(), authorization).stream(); } }).collect(Collectors.toList()); diff --git a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java index eee504ad22..f422b5e6f8 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/permission/AbstractPermissionService.java @@ -77,7 +77,7 @@ public class AbstractPermissionService { if (!resourceNotProvider) { if (resourceSetId != null) { - resource = storeFactory.getResourceStore().findById(resourceSetId); + resource = storeFactory.getResourceStore().findById(resourceSetId, resourceServer.getId()); } else { resource = storeFactory.getResourceStore().findByName(resourceSetName, this.resourceServer.getId()); } @@ -113,7 +113,7 @@ public class AbstractPermissionService { } } - for (Resource baseResource : authorization.getStoreFactory().getResourceStore().findByType(resource.getType())) { + for (Resource baseResource : authorization.getStoreFactory().getResourceStore().findByType(resource.getType(), resourceServer.getId())) { if (baseResource.getOwner().equals(resource.getResourceServer().getClientId())) { for (Scope baseScope : baseResource.getScopes()) { if (baseScope.getName().equals(scopeName)) { diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java index fdaa12f951..219bccacc8 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java @@ -109,7 +109,7 @@ public class ResourceService { } private Set findAll() { - Response response = this.resourceManager.find(null, null, null, null, null, -1, -1); + Response response = this.resourceManager.find(null, null, null, null, null, null, true, -1, -1); List resources = (List) response.getEntity(); return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet()); } @@ -150,7 +150,7 @@ public class ResourceService { } } } else { - resources = storeFactory.getResourceStore().findByOwner(identity.getId()).stream() + resources = storeFactory.getResourceStore().findByOwner(identity.getId(), resourceServer.getId()).stream() .map(resource -> ModelToRepresentation.toRepresentation(resource, this.resourceServer, authorization)) .collect(Collectors.toSet()); } 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 116ddd69be..3d9f9232bd 100644 --- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java +++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java @@ -18,9 +18,18 @@ package org.keycloak.authorization.util; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.Decision.Effect; import org.keycloak.authorization.identity.Identity; +import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; @@ -31,16 +40,6 @@ import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.representations.idm.authorization.Permission; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - /** * @author Pedro Igor */ @@ -62,8 +61,8 @@ public final class Permissions { StoreFactory storeFactory = authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); - resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization))); - resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization))); + resourceStore.findByOwner(resourceServer.getClientId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization))); + resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resource.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()), authorization))); return permissions; } @@ -81,7 +80,7 @@ public final class Permissions { if (type != null && !resource.getOwner().equals(resourceServer.getClientId())) { StoreFactory storeFactory = authorization.getStoreFactory(); ResourceStore resourceStore = storeFactory.getResourceStore(); - resourceStore.findByType(type).forEach(resource1 -> { + resourceStore.findByType(type, resourceServer.getId()).forEach(resource1 -> { if (resource1.getOwner().equals(resourceServer.getClientId())) { for (Scope typeScope : resource1.getScopes()) { if (!scopes.contains(typeScope)) { @@ -95,78 +94,115 @@ public final class Permissions { ScopeStore scopeStore = authorization.getStoreFactory().getScopeStore(); scopes = requestedScopes.stream().map(scopeName -> { Scope byName = scopeStore.findByName(scopeName, resource.getResourceServer().getId()); + + if (byName == null) { + throw new RuntimeException("Invalid scope [" + scopeName + "]."); + } + return byName; }).collect(Collectors.toList()); } - if (scopes.isEmpty()) { - permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer())); - } else { - for (Scope scope : scopes) { - permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer())); - } - } + permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer())); return permissions; } - public static List allPermits(List evaluation, AuthorizationProvider authorizationProvider) { + public static List allPermits(List evaluation, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) { + Map permissions = new HashMap<>(); + + for (Result evaluationResult : evaluation) { + ResourcePermission permission = evaluationResult.getPermission(); + + // if overall decision was a DENY, check for scope-based policies for a PERMIT + if (evaluationResult.getEffect().equals(Effect.DENY)) { + for (Result.PolicyResult result : evaluationResult.getResults()) { + Policy policy = result.getPolicy(); + + if ("scope".equals(policy.getType())) { + Set resources = policy.getResources(); + + if (Effect.PERMIT.equals(result.getStatus())) { + List scopes = policy.getScopes().stream().collect(Collectors.toList()); + + if (!resources.isEmpty()) { + resources.forEach(resource -> grantPermission(authorizationProvider, permissions, new ResourcePermission(resource, scopes, policy.getResourceServer()), resourceServer.getId())); + } else { + grantPermission(authorizationProvider, permissions, new ResourcePermission(permission.getResource(), scopes, policy.getResourceServer()), resourceServer.getId()); + } + } + } + } + continue; + } + + grantPermission(authorizationProvider, permissions, permission, resourceServer.getId()); + } + + return permissions.values().stream().collect(Collectors.toList()); + } + + public static List permits(List evaluation, AuthorizationProvider authorizationProvider, String resourceServer) { Map permissions = new HashMap<>(); for (Result evaluationResult : evaluation) { ResourcePermission permission = evaluationResult.getPermission(); - Set scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()); if (evaluationResult.getEffect().equals(Effect.DENY)) { continue; } - List resources = new ArrayList<>(); - Resource resource = permission.getResource(); - - if (resource != null) { - resources.add(resource); - } else { - List permissionScopes = permission.getScopes(); - - if (!permissionScopes.isEmpty()) { - ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); - resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()).toArray(new String[permissionScopes.size()]))); - } - } - - if (!resources.isEmpty()) { - for (Resource allowedResource : resources) { - String resourceId = allowedResource.getId(); - String resourceName = allowedResource.getName(); - Permission evalPermission = permissions.get(allowedResource.getId()); - - if (evalPermission == null) { - evalPermission = new Permission(resourceId, resourceName, scopes); - permissions.put(resourceId, evalPermission); - } - - if (scopes != null && !scopes.isEmpty()) { - Set finalScopes = evalPermission.getScopes(); - - if (finalScopes == null) { - finalScopes = new HashSet(); - evalPermission.setScopes(finalScopes); - } - - for (String scopeName : scopes) { - if (!finalScopes.contains(scopeName)) { - finalScopes.add(scopeName); - } - } - } - } - } else { - Permission scopePermission = new Permission(null, null, scopes); - permissions.put(scopePermission.toString(), scopePermission); - } + grantPermission(authorizationProvider, permissions, permission, resourceServer); } return permissions.values().stream().collect(Collectors.toList()); } + + private static void grantPermission(AuthorizationProvider authorizationProvider, Map permissions, ResourcePermission permission, String resourceServer) { + List resources = new ArrayList<>(); + Resource resource = permission.getResource(); + Set scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet()); + + if (resource != null) { + resources.add(resource); + } else { + List permissionScopes = permission.getScopes(); + + if (!permissionScopes.isEmpty()) { + ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); + resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer)); + } + } + + if (!resources.isEmpty()) { + for (Resource allowedResource : resources) { + String resourceId = allowedResource.getId(); + String resourceName = allowedResource.getName(); + Permission evalPermission = permissions.get(allowedResource.getId()); + + if (evalPermission == null) { + evalPermission = new Permission(resourceId, resourceName, scopes); + permissions.put(resourceId, evalPermission); + } + + if (scopes != null && !scopes.isEmpty()) { + Set finalScopes = evalPermission.getScopes(); + + if (finalScopes == null) { + finalScopes = new HashSet(); + evalPermission.setScopes(finalScopes); + } + + for (String scopeName : scopes) { + if (!finalScopes.contains(scopeName)) { + finalScopes.add(scopeName); + } + } + } + } + } else { + Permission scopePermission = new Permission(null, null, scopes); + permissions.put(scopePermission.toString(), scopePermission); + } + } } diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index df7f8d352f..29b89428bd 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -17,18 +17,27 @@ package org.keycloak.exportimport.util; -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; import org.keycloak.common.Version; import org.keycloak.common.util.Base64; @@ -63,20 +72,11 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.util.JsonSerialization; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; /** * @author Marek Posolda @@ -295,7 +295,6 @@ public class ExportUtils { rep.getOwner().setId(null); } rep.setId(null); - rep.setPolicies(null); rep.getScopes().forEach(scopeRepresentation -> { scopeRepresentation.setId(null); scopeRepresentation.setIconUri(null); @@ -338,10 +337,9 @@ public class ExportUtils { RealmModel realm = authorizationProvider.getRealm(); StoreFactory storeFactory = authorizationProvider.getStoreFactory(); try { - PolicyRepresentation rep = toRepresentation(policy, authorizationProvider); + PolicyRepresentation rep = toRepresentation(policy); rep.setId(null); - rep.setDependentPolicies(null); Map config = rep.getConfig(); @@ -363,20 +361,18 @@ public class ExportUtils { config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, realm).getUsername()).collect(Collectors.toList()))); } - String scopes = config.get("scopes"); + Set scopes = policy.getScopes(); - if (scopes != null && !scopes.isEmpty()) { - ScopeStore scopeStore = storeFactory.getScopeStore(); - List scopeIds = JsonSerialization.readValue(scopes, List.class); - config.put("scopes", JsonSerialization.writeValueAsString(scopeIds.stream().map(scopeId -> scopeStore.findById(scopeId).getName()).collect(Collectors.toList()))); + if (!scopes.isEmpty()) { + List scopeNames = scopes.stream().map(Scope::getName).collect(Collectors.toList()); + config.put("scopes", JsonSerialization.writeValueAsString(scopeNames)); } - String policyResources = config.get("resources"); + Set policyResources = policy.getResources(); - if (policyResources != null && !policyResources.isEmpty()) { - ResourceStore resourceStore = storeFactory.getResourceStore(); - List resourceIds = JsonSerialization.readValue(policyResources, List.class); - config.put("resources", JsonSerialization.writeValueAsString(resourceIds.stream().map(resourceId -> resourceStore.findById(resourceId).getName()).collect(Collectors.toList()))); + if (!policyResources.isEmpty()) { + List resourceNames = scopes.stream().map(Scope::getName).collect(Collectors.toList()); + config.put("resources", JsonSerialization.writeValueAsString(resourceNames)); } Set associatedPolicies = policy.getAssociatedPolicies(); @@ -385,8 +381,6 @@ public class ExportUtils { config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList()))); } - rep.setAssociatedPolicies(null); - return rep; } catch (Exception e) { throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java index 1fa5fc737a..b3a04754db 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/authorization/TestPolicyProviderFactory.java @@ -18,7 +18,6 @@ package org.keycloak.testsuite.authorization; import org.keycloak.Config; import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; @@ -43,8 +42,8 @@ public class TestPolicyProviderFactory implements PolicyProviderFactory { } @Override - public PolicyProvider create(Policy policy, AuthorizationProvider authorization) { - return new TestPolicyProvider(policy, authorization); + public PolicyProvider create(AuthorizationProvider authorization) { + return new TestPolicyProvider(authorization); } @Override @@ -79,11 +78,9 @@ public class TestPolicyProviderFactory implements PolicyProviderFactory { private class TestPolicyProvider implements PolicyProvider { - private final Policy policy; private final AuthorizationProvider authorization; - public TestPolicyProvider(Policy policy, AuthorizationProvider authorization) { - this.policy = policy; + public TestPolicyProvider(AuthorizationProvider authorization) { this.authorization = authorization; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java index e6f83d8af0..a1a2bfc628 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java @@ -38,6 +38,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -71,7 +72,7 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { PolicyRepresentation newPolicy = createTestingPolicy().toRepresentation(); assertEquals("Test Generic Policy", newPolicy.getName()); - assertEquals("test", newPolicy.getType()); + assertEquals("scope", newPolicy.getType()); assertEquals(Logic.POSITIVE, newPolicy.getLogic()); assertEquals(DecisionStrategy.UNANIMOUS, newPolicy.getDecisionStrategy()); assertEquals("configuration for A", newPolicy.getConfig().get("configA")); @@ -98,28 +99,28 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { @Test public void testUpdate() { PolicyResource policyResource = createTestingPolicy(); - PolicyRepresentation resource = policyResource.toRepresentation(); + PolicyRepresentation policy = policyResource.toRepresentation(); - resource.setName("changed"); - resource.setLogic(Logic.NEGATIVE); - resource.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE); - resource.getConfig().put("configA", "changed configuration for A"); - resource.getConfig().remove("configB"); - resource.getConfig().put("configC", "changed configuration for C"); + policy.setName("changed"); + policy.setLogic(Logic.NEGATIVE); + policy.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE); + policy.getConfig().put("configA", "changed configuration for A"); + policy.getConfig().remove("configB"); + policy.getConfig().put("configC", "changed configuration for C"); - policyResource.update(resource); + policyResource.update(policy); - resource = policyResource.toRepresentation(); + policy = policyResource.toRepresentation(); - assertEquals("changed", resource.getName()); - assertEquals(Logic.NEGATIVE, resource.getLogic()); + assertEquals("changed", policy.getName()); + assertEquals(Logic.NEGATIVE, policy.getLogic()); - assertEquals(DecisionStrategy.AFFIRMATIVE, resource.getDecisionStrategy()); - assertEquals("changed configuration for A", resource.getConfig().get("configA")); - assertNull(resource.getConfig().get("configB")); - assertEquals("changed configuration for C", resource.getConfig().get("configC")); + assertEquals(DecisionStrategy.AFFIRMATIVE, policy.getDecisionStrategy()); + assertEquals("changed configuration for A", policy.getConfig().get("configA")); + assertNull(policy.getConfig().get("configB")); + assertEquals("changed configuration for C", policy.getConfig().get("configC")); - Map config = resource.getConfig(); + Map config = policy.getConfig(); config.put("applyPolicies", buildConfigOption(findPolicyByName("Test Associated C").getId())); @@ -127,22 +128,25 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { config.put("scopes", buildConfigOption(findScopeByName("Test Scope A").getId())); - policyResource.update(resource); + policyResource.update(policy); - resource = policyResource.toRepresentation(); - config = resource.getConfig(); + policy = policyResource.toRepresentation(); + config = policy.getConfig(); - assertAssociatedPolicy("Test Associated C", resource); - assertFalse(config.get("applyPolicies").contains(findPolicyByName("Test Associated A").getId())); - assertFalse(config.get("applyPolicies").contains(findPolicyByName("Test Associated B").getId())); + assertAssociatedPolicy("Test Associated C", policy); + List associatedPolicies = getClientResource().authorization().policies().policy(policy.getId()).associatedPolicies(); + assertFalse(associatedPolicies.stream().filter(associated -> associated.getId().equals(findPolicyByName("Test Associated A").getId())).findFirst().isPresent()); + assertFalse(associatedPolicies.stream().filter(associated -> associated.getId().equals(findPolicyByName("Test Associated B").getId())).findFirst().isPresent()); - assertAssociatedResource("Test Resource B", resource); - assertFalse(config.get("resources").contains(findResourceByName("Test Resource A").getId())); - assertFalse(config.get("resources").contains(findResourceByName("Test Resource C").getId())); + assertAssociatedResource("Test Resource B", policy); + List resources = policyResource.resources(); + assertFalse(resources.contains(findResourceByName("Test Resource A"))); + assertFalse(resources.contains(findResourceByName("Test Resource C"))); - assertAssociatedScope("Test Scope A", resource); - assertFalse(config.get("scopes").contains(findScopeByName("Test Scope B").getId())); - assertFalse(config.get("scopes").contains(findScopeByName("Test Scope C").getId())); + assertAssociatedScope("Test Scope A", policy); + List scopes = getClientResource().authorization().policies().policy(policy.getId()).scopes(); + assertFalse(scopes.contains(findScopeByName("Test Scope B").getId())); + assertFalse(scopes.contains(findScopeByName("Test Scope C").getId())); } @Test @@ -186,7 +190,7 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { PolicyRepresentation newPolicy = new PolicyRepresentation(); newPolicy.setName(name); - newPolicy.setType("test"); + newPolicy.setType("scope"); newPolicy.setConfig(config); PoliciesResource policies = getClientResource().authorization().policies(); @@ -264,27 +268,38 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { private void assertAssociatedPolicy(String associatedPolicyName, PolicyRepresentation dependentPolicy) { PolicyRepresentation associatedPolicy = findPolicyByName(associatedPolicyName); + PoliciesResource policies = getClientResource().authorization().policies(); + associatedPolicy = policies.policy(associatedPolicy.getId()).toRepresentation(); assertNotNull(associatedPolicy); - assertTrue(dependentPolicy.getConfig().get("applyPolicies").contains(associatedPolicy.getId())); - assertEquals(1, associatedPolicy.getDependentPolicies().size()); - assertEquals(dependentPolicy.getId(), associatedPolicy.getDependentPolicies().get(0).getId()); + PolicyRepresentation finalAssociatedPolicy = associatedPolicy; + PolicyResource policyResource = policies.policy(dependentPolicy.getId()); + List associatedPolicies = policyResource.associatedPolicies(); + assertTrue(associatedPolicies.stream().filter(associated -> associated.getId().equals(finalAssociatedPolicy.getId())).findFirst().isPresent()); + List dependentPolicies = policies.policy(associatedPolicy.getId()).dependentPolicies(); + assertEquals(1, dependentPolicies.size()); + assertEquals(dependentPolicy.getId(), dependentPolicies.get(0).getId()); } private void assertAssociatedResource(String resourceName, PolicyRepresentation policy) { ResourceRepresentation resource = findResourceByName(resourceName); assertNotNull(resource); - assertTrue(policy.getConfig().get("resources").contains(resource.getId())); - assertEquals(1, resource.getPolicies().size()); - assertTrue(resource.getPolicies().stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) + List resources = getClientResource().authorization().policies().policy(policy.getId()).resources(); + assertTrue(resources.contains(resource)); + List policies = getClientResource().authorization().resources().resource(resource.getId()).permissions(); + assertEquals(1, policies.size()); + assertTrue(policies.stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) .contains(policy.getId())); } private void assertAssociatedScope(String scopeName, PolicyRepresentation policy) { ScopeRepresentation scope = findScopeByName(scopeName); + scope = getClientResource().authorization().scopes().scope(scope.getId()).toRepresentation(); assertNotNull(scope); - assertTrue(policy.getConfig().get("scopes").contains(scope.getId())); - assertEquals(1, scope.getPolicies().size()); - assertTrue(scope.getPolicies().stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) + List scopes = getClientResource().authorization().policies().policy(policy.getId()).scopes(); + assertTrue(scopes.stream().map((Function) rep -> rep.getId()).collect(Collectors.toList()).contains(scope.getId())); + List permissions = getClientResource().authorization().scopes().scope(scope.getId()).permissions(); + assertEquals(1, permissions.size()); + assertTrue(permissions.stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) .contains(policy.getId())); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java index c9b69d8bff..c46b338957 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java @@ -101,7 +101,7 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest // during tests we create resource instances, but we need to reload them to get their collections updated List updatedPermissions = permissions.stream().map(permission -> { - Resource resource = storeFactory.getResourceStore().findById(permission.getResource().getId()); + Resource resource = storeFactory.getResourceStore().findById(permission.getResource().getId(), resourceServer.getId()); return new ResourcePermission(resource, permission.getScopes(), permission.getResourceServer()); }).collect(Collectors.toList()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java index 086b5ac94c..4a4fc9a4ad 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java @@ -53,7 +53,7 @@ public class ResourceManagementTest extends AbstractPhotozAdminTest { ResourceRepresentation resource = response.readEntity(ResourceRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId()); + Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId(), resourceServer.getId()); assertNotNull(resourceModel); assertEquals(resource.getId(), resourceModel.getId()); @@ -89,7 +89,7 @@ public class ResourceManagementTest extends AbstractPhotozAdminTest { ResourceRepresentation resource = response.readEntity(ResourceRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId()); + Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId(), resourceServer.getId()); assertNotNull(resourceModel); assertEquals(resource.getId(), resourceModel.getId()); @@ -147,7 +147,7 @@ public class ResourceManagementTest extends AbstractPhotozAdminTest { assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus()); onAuthorizationSession(authorizationProvider -> { - Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId()); + Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId(), resourceServer.getId()); assertNull(resourceModel); }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java index 9ecbc3d992..4708a2a2c8 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java @@ -46,8 +46,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -77,7 +81,7 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId()); + Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId()); assertNotNull(policyModel); assertEquals(permission.getId(), policyModel.getId()); @@ -357,7 +361,7 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId()); + Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId()); assertNotNull(policyModel); assertEquals(permission.getId(), policyModel.getId()); @@ -430,7 +434,8 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { config.put("defaultResourceType", albumResource.getType()); - String applyPolicies = JsonSerialization.writeValueAsString(new String[]{this.anyUserPolicy.getId(), this.administrationPolicy.getId()}); + String[] associatedPolicies = {this.anyUserPolicy.getId(), this.administrationPolicy.getId()}; + String applyPolicies = JsonSerialization.writeValueAsString(associatedPolicies); config.put("applyPolicies", applyPolicies); @@ -443,14 +448,15 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId()); + Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId()); assertNotNull(policyModel); assertEquals(permission.getId(), policyModel.getId()); assertEquals(permission.getName(), policyModel.getName()); assertEquals(permission.getType(), policyModel.getType()); assertTrue(permission.getConfig().containsValue(albumResource.getType())); - assertTrue(permission.getConfig().containsValue(applyPolicies)); + assertTrue(policyModel.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toList()).containsAll(Arrays.asList(associatedPolicies))); + assertEquals(resourceServer.getId(), policyModel.getResourceServer().getId()); }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java index b2e1a421ea..644883ff9c 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java @@ -57,7 +57,7 @@ public class ScopeManagementTest extends AbstractPhotozAdminTest { ScopeRepresentation scope = response.readEntity(ScopeRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId()); + Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId(), resourceServer.getId()); assertNotNull(scopeModel); assertEquals(scope.getId(), scopeModel.getId()); @@ -86,7 +86,7 @@ public class ScopeManagementTest extends AbstractPhotozAdminTest { ScopeRepresentation scope = response.readEntity(ScopeRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId()); + Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId(), resourceServer.getId()); assertNotNull(scopeModel); assertEquals(scope.getId(), scopeModel.getId()); @@ -138,7 +138,7 @@ public class ScopeManagementTest extends AbstractPhotozAdminTest { assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus()); onAuthorizationSession(authorizationProvider -> { - Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId()); + Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId(), resourceServer.getId()); assertNull(scopeModel); }); diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json index 3f4ddd1a1e..27e9f5ec9e 100755 --- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json @@ -64,7 +64,7 @@ "connectionsJpa": { "default": { - "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test}", + "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1}", "driver": "${keycloak.connectionsJpa.driver:org.h2.Driver}", "driverDialect": "${keycloak.connectionsJpa.driverDialect:}", "user": "${keycloak.connectionsJpa.user:sa}", diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 18ffe2a392..e6188e811f 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1031,6 +1031,10 @@ authz-result=Result authz-authorization-services-enabled=Authorization Enabled authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client authz-required=Required +authz-show-details=Show Details +authz-hide-details=Hide Details +authz-associated-permissions=Associated Permissions +authz-no-permission-associated=No permissions associated # Authz Settings authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server. @@ -1051,6 +1055,7 @@ authz-export-settings.tooltip=Export and download all authorization settings for authz-no-resources-available=No resources available. authz-no-scopes-assigned=No scopes assigned. authz-no-type-defined=No type defined. +authz-no-uri-defined=No URI defined. authz-no-permission-assigned=No permission assigned. authz-no-policy-assigned=No policy assigned. authz-create-permission=Create permission diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index ea039c195a..2a86b93687 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -86,10 +86,13 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $scope.query = { realm: realm.realm, client : client.id, + deep: false, max : 20, first : 0 }; + $scope.listSizes = [5, 10, 20]; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id @@ -124,20 +127,86 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $scope.searchQuery = function() { $scope.searchLoaded = false; - $scope.resources = ResourceServerResource.query($scope.query, function() { + ResourceServerResource.query($scope.query, function(response) { $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + $scope.resources = response; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (resource) { + if (resource.details) { + resource.details.loaded = !resource.details.loaded; + return; + } + + resource.details = {loaded: false}; + + ResourceServerResource.scopes({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resource._id + }, function(response) { + resource.scopes = response; + ResourceServerResource.permissions({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resource._id + }, function(response) { + resource.policies = response; + resource.details.loaded = true; + }); + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.resources.length; i++) { + $scope.loadDetails($scope.resources[i]); + } + } + }; }); module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerResource, ResourceServerScope, AuthzDialog, Notifications) { $scope.realm = realm; $scope.client = client; - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + return object.name; + }, + formatSelection: function(object, container, query) { + return object.name; + } + }; var $instance = this; @@ -165,6 +234,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r }, true); $scope.save = function() { + for (i = 0; i < $scope.resource.scopes.length; i++) { + delete $scope.resource.scopes[i].text; + } $instance.checkNameAvailability(function () { ResourceServerResource.save({realm : realm.realm, client : $scope.client.id}, $scope.resource, function(data) { $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource/" + data._id); @@ -186,17 +258,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r data.scopes = []; } - if (!data.policies) { - data.policies = []; - } - $scope.resource = angular.copy(data); $scope.changed = false; - for (i = 0; i < $scope.resource.scopes.length; i++) { - $scope.resource.scopes[i] = $scope.resource.scopes[i].name; - } - $scope.originalResource = angular.copy($scope.resource); $scope.$watch('resource', function() { @@ -206,6 +270,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r }, true); $scope.save = function() { + for (i = 0; i < $scope.resource.scopes.length; i++) { + delete $scope.resource.scopes[i].text; + } $instance.checkNameAvailability(function () { ResourceServerResource.update({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, $scope.resource, function() { $route.reload(); @@ -215,22 +282,28 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r } $scope.remove = function() { - var msg = ""; + ResourceServerResource.permissions({ + realm : $route.current.params.realm, + client : client.id, + rsrid : $scope.resource._id + }, function (permissions) { + var msg = ""; - if ($scope.resource.policies.length > 0) { - msg = "

This resource is referenced in some policies:

"; - msg += "
    "; - for (i = 0; i < $scope.resource.policies.length; i++) { - msg+= "
  • " + $scope.resource.policies[i].name + "
  • "; + if (permissions.length > 0 && !$scope.deleteConsent) { + msg = "

    This resource is referenced in some policies:

    "; + msg += "
      "; + for (i = 0; i < permissions.length; i++) { + msg+= "
    • " + permissions[i].name + "
    • "; + } + msg += "
    "; + msg += "

    If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.

    "; } - msg += "
"; - msg += "

If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.

"; - } - AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() { - ResourceServerResource.delete({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() { - $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource"); - Notifications.success("The resource has been deleted."); + AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() { + ResourceServerResource.delete({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() { + $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource"); + Notifications.success("The resource has been deleted."); + }); }); }); } @@ -269,10 +342,13 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo $scope.query = { realm: realm.realm, client : client.id, + deep: false, max : 20, first : 0 }; + $scope.listSizes = [5, 10, 20]; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id @@ -304,14 +380,53 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo $scope.searchQuery(); } - $scope.searchQuery = function() { + $scope.searchQuery = function(detailsFilter) { $scope.searchLoaded = false; - $scope.scopes = ResourceServerScope.query($scope.query, function() { + ResourceServerScope.query($scope.query, function(response) { + $scope.scopes = response; $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (scope) { + if (scope.details) { + scope.details.loaded = !scope.details.loaded; + return; + } + + scope.details = {loaded: false}; + + ResourceServerScope.resources({ + realm : $route.current.params.realm, + client : client.id, + id : scope.id + }, function(response) { + scope.resources = response; + ResourceServerScope.permissions({ + realm : $route.current.params.realm, + client : client.id, + id : scope.id + }, function(response) { + scope.policies = response; + scope.details.loaded = true; + }); + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.scopes.length; i++) { + $scope.loadDetails($scope.scopes[i]); + } + } + }; }); module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerScope, AuthzDialog, Notifications) { @@ -377,22 +492,28 @@ module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $rout } $scope.remove = function() { - var msg = ""; + ResourceServerScope.permissions({ + realm : $route.current.params.realm, + client : client.id, + id : $scope.scope.id + }, function (permissions) { + var msg = ""; - if ($scope.scope.policies.length > 0) { - msg = "

This resource is referenced in some policies:

"; - msg += "
    "; - for (i = 0; i < $scope.scope.policies.length; i++) { - msg+= "
  • " + $scope.scope.policies[i].name + "
  • "; + if (permissions.length > 0 && !$scope.deleteConsent) { + msg = "

    This scope is referenced in some policies:

    "; + msg += "
      "; + for (i = 0; i < permissions.length; i++) { + msg+= "
    • " + permissions[i].name + "
    • "; + } + msg += "
    "; + msg += "

    If you remove this scope, the policies above will be affected and will not be associated with this scope anymore.

    "; } - msg += "
"; - msg += "

If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.

"; - } - AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() { - ResourceServerScope.delete({realm : realm.realm, client : $scope.client.id, id : $scope.scope.id}, null, function() { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/scope"); - Notifications.success("The scope has been deleted."); + AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() { + ResourceServerScope.delete({realm : realm.realm, client : $scope.client.id, id : $scope.scope.id}, null, function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/scope"); + Notifications.success("The scope has been deleted."); + }); }); }); } @@ -432,10 +553,12 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l realm: realm.realm, client : client.id, permission: false, - max : 20, + max: 20, first : 0 }; + $scope.listSizes = [5, 10, 20]; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -481,18 +604,42 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l $scope.searchLoaded = false; ResourceServerPolicy.query($scope.query, function(data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); - } - } - + $scope.policies = data; $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (policy) { + if (policy.details) { + policy.details.loaded = !policy.details.loaded; + return; + } + + policy.details = {loaded: false}; + + ResourceServerPolicy.dependentPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(response) { + policy.dependentPolicies = response; + policy.details.loaded = true; + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.policies.length; i++) { + $scope.loadDetails($scope.policies[i]); + } + } + }; }); module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client) { @@ -508,6 +655,8 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route first : 0 }; + $scope.listSizes = [5, 10, 20]; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -553,18 +702,42 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route $scope.searchLoaded = false; ResourceServerPolicy.query($scope.query, function(data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type == 'resource' || data[i].type == 'scope') { - $scope.policies.push(data[i]); - } - } - + $scope.policies = data; $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (policy) { + if (policy.details) { + policy.details.loaded = !policy.details.loaded; + return; + } + + policy.details = {loaded: false}; + + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(response) { + policy.associatedPolicies = response; + policy.details.loaded = true; + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.policies.length; i++) { + $scope.loadDetails($scope.policies[i]); + } + } + }; }); module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) { @@ -623,19 +796,64 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro }, onInit : function() { - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); - - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; } - }); + }; + + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; $scope.applyToResourceType = function() { if ($scope.policy.config.default) { @@ -648,30 +866,69 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro onInitUpdate : function(policy) { policy.config.default = eval(policy.config.default); - policy.config.resources = eval(policy.config.resources); - policy.config.applyPolicies = eval(policy.config.applyPolicies); + policy.config.resources = {}; + ResourceServerPolicy.resources({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(resources) { + resources[0].text = resources[0].name; + $scope.policy.config.resources = resources[0]; + }); + + policy.config.applyPolicies = []; + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.policy.config.applyPolicies.push(policies[i]); + } + }); }, onUpdate : function() { - $scope.policy.config.resources = JSON.stringify($scope.policy.config.resources); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); }, onInitCreate : function(newPolicy) { newPolicy.decisionStrategy = 'UNANIMOUS'; newPolicy.config = {}; - newPolicy.config.resources = ''; + newPolicy.config.resources = null; var resourceId = $location.search()['rsrid']; if (resourceId) { - newPolicy.config.resources = [resourceId]; + ResourceServerResource.get({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resourceId + }, function(data) { + data.text = data.name; + $scope.policy.config.resources = data; + }); } }, onCreate : function() { - $scope.policy.config.resources = JSON.stringify($scope.policy.config.resources); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); + + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); } }, realm, client, $scope); }); @@ -687,105 +944,241 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route }, onInit : function() { - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); - - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; } - } - }); - - $scope.resolveScopes = function(policy, keepScopes) { - if (!keepScopes) { - policy.config.scopes = []; - } - - if (!policy) { - policy = $scope.policy; - } - - if (policy.config.resources != null) { - ResourceServerResource.get({ - realm : $route.current.params.realm, + $scope.query = { + realm: realm.realm, client : client.id, - rsrid : policy.config.resources - }, function(data) { - $scope.scopes = data.scopes; + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); }); - } else { - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.selectResource = function() { + if ($scope.policy.config.resources) { + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: $scope.policy.config.resources._id + }, function (data) { + $scope.policy.config.resources.scopes = data; }); } } }, onInitUpdate : function(policy) { - if (policy.config.resources) { - policy.config.resources = eval(policy.config.resources); + policy.config.resources = eval(policy.config.resources); - if (policy.config.resources.length > 0) { - policy.config.resources = policy.config.resources[0]; - } else { - policy.config.resources = null; - } + if (policy.config.resources == null) { + policy.config.resources = []; } - $scope.resolveScopes(policy, true); + if (policy.config.resources.length > 0) { + ResourceServerResource.query({ + realm: $route.current.params.realm, + client: client.id, + _id: policy.config.resources[0], + deep: false + }, function (data) { + data[0].text = data[0].name; + $scope.policy.config.resources = data[0]; + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: policy.config.resources[0] + }, function (data) { + $scope.policy.config.resources.scopes = data; + }); + ResourceServerPolicy.scopes({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(scopes) { + $scope.policy.config.scopes = []; + for (i = 0; i < scopes.length; i++) { + $scope.policy.config.scopes.push(scopes[i].id); + } + }); + }); + } else { + policy.config.resources = null; + ResourceServerPolicy.scopes({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(scopes) { + $scope.policy.config.scopes = []; + for (i = 0; i < scopes.length; i++) { + scopes[i].text = scopes[i].name; + $scope.policy.config.scopes.push(scopes[i]); + } + }); + } - policy.config.applyPolicies = eval(policy.config.applyPolicies); - policy.config.scopes = eval(policy.config.scopes); + policy.config.applyPolicies = []; + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.policy.config.applyPolicies.push(policies[i]); + } + }); }, onUpdate : function() { if ($scope.policy.config.resources != null) { - var resources = undefined; - - if ($scope.policy.config.resources.length != 0) { - resources = JSON.stringify([$scope.policy.config.resources]) - } - - $scope.policy.config.resources = resources; + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); } - $scope.policy.config.scopes = JSON.stringify($scope.policy.config.scopes); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + var scopes = []; + + for (i = 0; i < $scope.policy.config.scopes.length; i++) { + if ($scope.policy.config.resources == null) { + scopes.push($scope.policy.config.scopes[i].id); + } else { + scopes.push($scope.policy.config.scopes[i]); + } + } + + $scope.policy.config.scopes = JSON.stringify(scopes); + + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); }, onInitCreate : function(newPolicy) { newPolicy.decisionStrategy = 'UNANIMOUS'; newPolicy.config = {}; - newPolicy.config.resources = ''; + newPolicy.config.resources = null; var scopeId = $location.search()['scpid']; if (scopeId) { - newPolicy.config.scopes = [scopeId]; + ResourceServerScope.get({ + realm: $route.current.params.realm, + client: client.id, + id: scopeId, + }, function (data) { + data.text = data.name; + if (!$scope.policy.config.scopes) { + $scope.policy.config.scopes = []; + } + $scope.policy.config.scopes.push(data); + }); } }, onCreate : function() { if ($scope.policy.config.resources != null) { - var resources = undefined; - - if ($scope.policy.config.resources.length != 0) { - resources = JSON.stringify([$scope.policy.config.resources]) - } - - $scope.policy.config.resources = resources; + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); } - $scope.policy.config.scopes = JSON.stringify($scope.policy.config.scopes); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + + var scopes = []; + + for (i = 0; i < $scope.policy.config.scopes.length; i++) { + if ($scope.policy.config.scopes[i].id) { + scopes.push($scope.policy.config.scopes[i].id); + } else { + scopes.push($scope.policy.config.scopes[i]); + } + } + + $scope.policy.config.scopes = JSON.stringify(scopes); + + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); } }, realm, client, $scope); }); @@ -1250,23 +1643,58 @@ module.controller('ResourceServerPolicyAggregateDetailCtrl', function($scope, $r }, onInit : function() { - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + }, + + onInitUpdate : function(policy) { + policy.config.applyPolicies = []; + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.policy.config.applyPolicies.push(policies[i]); } }); }, - onInitUpdate : function(policy) { - policy.config.applyPolicies = eval(policy.config.applyPolicies); - }, - onUpdate : function() { - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); }, onInitCreate : function(newPolicy) { @@ -1275,7 +1703,13 @@ module.controller('ResourceServerPolicyAggregateDetailCtrl', function($scope, $r }, onCreate : function() { - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); } }, realm, client, $scope); }); @@ -1357,9 +1791,9 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe } } else { ResourceServerPolicy.get({ - realm : $route.current.params.realm, + realm: realm.realm, client : client.id, - id : $route.current.params.id, + id: $route.current.params.id }, function(data) { $scope.originalPolicy = data; var policy = angular.copy(data); @@ -1408,25 +1842,31 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe $scope.remove = function() { var msg = ""; - if ($scope.policy.dependentPolicies.length > 0) { - msg = "

This policy is being used by other policies:

"; - msg += "
    "; - for (i = 0; i < $scope.policy.dependentPolicies.length; i++) { - msg+= "
  • " + $scope.policy.dependentPolicies[i].name + "
  • "; - } - msg += "
"; - msg += "

If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.

"; - } - - AuthzDialog.confirmDeleteWithMsg($scope.policy.name, "Policy", msg, function() { - ResourceServerPolicy.delete({realm : $scope.realm.realm, client : $scope.client.id, id : $scope.policy.id}, null, function() { - if (delegate.isPermission()) { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission"); - Notifications.success("The permission has been deleted."); - } else { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy"); - Notifications.success("The policy has been deleted."); + ResourceServerPolicy.dependentPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : $scope.policy.id + }, function (dependentPolicies) { + if (dependentPolicies.length > 0 && !$scope.deleteConsent) { + msg = "

This policy is being used by other policies:

"; + msg += "
    "; + for (i = 0; i < dependentPolicies.length; i++) { + msg+= "
  • " + dependentPolicies[i].name + "
  • "; } + msg += "
"; + msg += "

If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.

"; + } + + AuthzDialog.confirmDeleteWithMsg($scope.policy.name, "Policy", msg, function() { + ResourceServerPolicy.delete({realm : $scope.realm.realm, client : $scope.client.id, id : $scope.policy.id}, null, function() { + if (delegate.isPermission()) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission"); + Notifications.success("The permission has been deleted."); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy"); + Notifications.success("The policy has been deleted."); + } + }); }); }); } @@ -1465,13 +1905,8 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio $scope.authzRequest.context = {}; $scope.authzRequest.context.attributes = {}; $scope.authzRequest.roleIds = []; - $scope.newResource = {}; $scope.resultUrl = resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate-result.html'; - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - $scope.addContextAttribute = function() { if (!$scope.newContextAttribute.value || $scope.newContextAttribute.value == '') { Notifications.error("You must provide a value to a context attribute."); @@ -1573,33 +2008,39 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio } $scope.setApplyToResourceType = function() { - if ($scope.applyResourceType) { - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - } - delete $scope.newResource; $scope.authzRequest.resources = []; } $scope.addResource = function() { - var resource = {}; + var resource = angular.copy($scope.newResource); - resource.id = $scope.newResource._id; + if (!resource) { + resource = {}; + } - for (i = 0; i < $scope.resources.length; i++) { - if ($scope.resources[i]._id == resource.id) { - resource.name = $scope.resources[i].name; - break; + delete resource.text; + + if (!$scope.newScopes || (resource._id != null && $scope.newScopes.length > 0 && $scope.newScopes[0].id)) { + $scope.newScopes = []; + } + + var scopes = []; + + for (i = 0; i < $scope.newScopes.length; i++) { + if ($scope.newScopes[i].name) { + scopes.push($scope.newScopes[i].name); + } else { + scopes.push($scope.newScopes[i]); } } - resource.scopes = $scope.newResource.scopes; + resource.scopes = scopes; $scope.authzRequest.resources.push(resource); delete $scope.newResource; + delete $scope.newScopes; } $scope.removeResource = function(index) { @@ -1610,20 +2051,11 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio if ($scope.newResource._id) { $scope.newResource.scopes = []; $scope.scopes = []; - ResourceServerResource.get({ + ResourceServerResource.scopes({ realm: $route.current.params.realm, - client : client.id, + client: client.id, rsrid: $scope.newResource._id }, function (data) { - $scope.scopes = data.scopes; - if (data.typedScopes) { - for (i=0;i 0 && $scope.newScopes[0].id)) { + $scope.newScopes = []; + } + + var scopes = angular.copy($scope.newScopes); + + for (i = 0; i < scopes.length; i++) { + delete scopes[i].text; + } + + $scope.authzRequest.resources[0].scopes = scopes; } $http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/evaluate' @@ -1697,9 +2139,64 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio } }; - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; ResourceServer.get({ realm : $route.current.params.realm, @@ -1717,5 +2214,4 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio $scope.authzRequest.userId = user.id; } - }); \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js index 795cf1d735..1c4f584bd8 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js @@ -16,7 +16,9 @@ module.factory('ResourceServerResource', function($resource) { rsrid : '@rsrid' }, { 'update' : {method : 'PUT'}, - 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/search', method : 'GET'} + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/search', method : 'GET'}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid/scopes', method : 'GET', isArray: true}, + 'permissions' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid/permissions', method : 'GET', isArray: true} }); }); @@ -27,7 +29,9 @@ module.factory('ResourceServerScope', function($resource) { id : '@id' }, { 'update' : {method : 'PUT'}, - 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/search', method : 'GET'} + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/search', method : 'GET'}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id/resources', method : 'GET', isArray: true}, + 'permissions' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id/permissions', method : 'GET', isArray: true}, }); }); @@ -38,7 +42,11 @@ module.factory('ResourceServerPolicy', function($resource) { id : '@id' }, { 'update' : {method : 'PUT'}, - 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/search', method : 'GET'} + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/search', method : 'GET'}, + 'associatedPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/associatedPolicies', method : 'GET', isArray: true}, + 'dependentPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/dependentPolicies', method : 'GET', isArray: true}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/scopes', method : 'GET', isArray: true}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/resources', method : 'GET', isArray: true} }); }); diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html index a8d45122e0..10e31707d0 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html @@ -39,9 +39,7 @@
- +
{{:: 'authz-permission-resource-resource.tooltip' | translate}} @@ -58,9 +56,7 @@
- +
{{:: 'authz-policy-apply-policy.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html index 3d1660b27e..90a3dc698b 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html @@ -32,12 +32,7 @@
- +
{{:: 'authz-permission-scope-resource.tooltip' | translate}} @@ -45,25 +40,19 @@
- - +
{{:: 'authz-permission-scope-scope.tooltip' | translate}} @@ -71,9 +60,7 @@
- +
{{:: 'authz-policy-apply-policy.tooltip' | translate}} @@ -95,7 +82,6 @@ -
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html index bfd1b22464..11a4d9d377 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html @@ -28,6 +28,12 @@
+
+ +
+
- + {{policy.name}} {{policy.description}} {{policy.type}} - - {{:: 'authz-no-policy-assigned' | translate}} - - - {{policy.name}}{{$last ? '' : ', '}} - - + + {{:: 'authz-show-details' | translate}} + + + {{:: 'authz-hide-details' | translate}} + + + + +
+ + + + + + + + + + + +
Dependent Permissions
+ {{:: 'authz-no-permission-assigned' | translate}} + +
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html index 7888f21897..2544d3c2d4 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html @@ -34,11 +34,8 @@
- +
- {{:: 'authz-policy-apply-policy.tooltip' | translate}}
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html index d8841507d8..dbaedd3ce4 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html @@ -172,14 +172,7 @@
- +
{{:: 'authz-permission-resource-resource.tooltip' | translate}}
@@ -199,12 +192,7 @@
-
{{:: 'authz-permission-scope-scope.tooltip' | translate}} @@ -215,7 +203,7 @@
@@ -246,11 +234,11 @@ {{resource.name ? resource.name : 'authz-evaluation-any-resource-with-scopes' | translate}} {{:: 'authz-any-scope' | translate}}. - - - {{scope}} {{$last ? '' : ', '}} + + + {{scope.name ? scope.name : scope}} {{$last ? '' : ', '}} + -
+
+ +
+ {{:: 'authz-hide-details' | translate}} + {{:: 'authz-show-details' | translate}} +
- + {{policy.name}} {{policy.description}} {{policy.type}} + + {{:: 'authz-show-details' | translate}} + + + {{:: 'authz-hide-details' | translate}} + + + + +
+ + + + + + + + + + + +
Dependent Permissions
+ {{:: 'authz-no-permission-assigned' | translate}} + +
+
+ {{:: 'no-results' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html index f61c6e9df8..ef20ae90ea 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-detail.html @@ -47,9 +47,7 @@
- +
{{:: 'authz-resource-scopes.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html index b1c3978988..1f8e1bd502 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html @@ -39,8 +39,13 @@ +
+ +
- @@ -52,9 +57,7 @@ {{:: 'type' | translate}} {{:: 'authz-uri' | translate}} {{:: 'authz-owner' | translate}} - {{:: 'authz-scopes' | translate}} - {{:: 'authz-permissions' | translate}} - {{:: 'actions' | translate}} + {{:: 'actions' | translate}} @@ -64,37 +67,67 @@ + - + {{resource.name}} {{resource.type}} {{:: 'authz-no-type-defined' | translate}} - {{resource.uri}} + + {{resource.uri}} + {{:: 'authz-no-uri-defined' | translate}} + {{resource.owner.name}} - - {{:: 'authz-no-scopes-assigned' | translate}} - - - {{scope.name}}{{$last ? '' : ', '}} - - + + {{:: 'authz-show-details' | translate}} - - {{:: 'authz-no-permission-assigned' | translate}} - - - {{policy.name}}{{$last ? '' : ', '}} - - + + {{:: 'authz-hide-details' | translate}} - - + + {{:: 'authz-create-permission' | translate}} + + + + +
+ + + + + + + + + + + + + +
ScopesAssociated Permissions
+ {{:: 'authz-no-scopes-assigned' | translate}} + + + {{:: 'authz-no-permission-assigned' | translate}} + +
+
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html index 78861293c9..ff8bb14060 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html @@ -14,6 +14,12 @@ +
+ +
{{:: 'create' | translate}} @@ -23,43 +29,68 @@ {{:: 'name' | translate}} - {{:: 'authz-resources' | translate}} - {{:: 'authz-permissions' | translate}} - {{:: 'actions' | translate}} + {{:: 'actions' | translate}} - +
+
- + {{scope.name}} - - {{:: 'authz-no-resources-assigned' | translate}} - - - {{resource.name}}{{$last ? '' : ', '}} - - + + {{:: 'authz-show-details' | translate}} - - {{:: 'authz-no-permission-assigned' | translate}} - - - {{policy.name}}{{$last ? '' : ', '}} - - + + {{:: 'authz-hide-details' | translate}} - - + + {{:: 'authz-create-permission' | translate}} + + + + +
+ + + + + + + + + + + + + +
ResourcesAssociated Permissions
+ {{:: 'authz-no-resources-assigned' | translate}} + + + {{:: 'authz-no-permission-assigned' | translate}} + +
+
diff --git a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css index 2eb155eb27..6f72a78cb3 100755 --- a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css +++ b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css @@ -390,4 +390,13 @@ h1 i { float: left; margin-left: 2%; width: 25%; +} + +table.kc-authz-table-expanded { + margin-top: 0px !important; +} + +.no-gutter > [class*='col-'] { + padding-right:0!important; + padding-left:0!important; } \ No newline at end of file