keycloak-uncached

Changes

Details

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 dfc22dd..5d91db7 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
@@ -285,7 +285,7 @@ public class CachedPolicyStore implements PolicyStore {
 
             @Override
             public void removeScope(Scope scope) {
-                getDelegateForUpdate().removeScope(scope);
+                getDelegateForUpdate().removeScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId()));
                 cached.removeScope(scope);
             }
 
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 3a0de26..ef2e19b 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
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 import org.infinispan.Cache;
 import org.keycloak.authorization.model.ResourceServer;
@@ -202,6 +203,19 @@ public class CachedScopeStore implements ScopeStore {
 
                 return this.updated;
             }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || !Scope.class.isInstance(o)) return false;
+                Scope that = (Scope) o;
+                return Objects.equals(getId(), that.getId());
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(getId());
+            }
         };
     }
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedScope.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedScope.java
index 6450119..3e931d8 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedScope.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/entities/CachedScope.java
@@ -22,6 +22,7 @@ import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 
 import java.io.Serializable;
+import java.util.Objects;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -79,4 +80,17 @@ public class CachedScope implements Scope, Serializable {
     public String getResourceServerId() {
         return this.resourceServerId;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !Scope.class.isInstance(o)) return false;
+        Scope that = (Scope) o;
+        return Objects.equals(id, that.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java
index 9f2c3b8..523f38a 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ScopeEntity.java
@@ -114,9 +114,9 @@ public class ScopeEntity implements Scope {
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        ScopeEntity that = (ScopeEntity) o;
-        return Objects.equals(id, that.id);
+        if (o == null || !Scope.class.isInstance(o)) return false;
+        Scope that = (Scope) o;
+        return Objects.equals(id, that.getId());
     }
 
     @Override
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
index 1eef22a..e2821e1 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/ResourcePermission.java
@@ -57,7 +57,7 @@ public class ResourcePermission {
      * @return a lit of permitted scopes
      */
     public List<Scope> getScopes() {
-        return Collections.unmodifiableList(this.scopes);
+        return this.scopes;
     }
 
     /**
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 2866360..02312ba 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
@@ -18,16 +18,16 @@
 
 package org.keycloak.authorization.policy.evaluation;
 
-import org.keycloak.authorization.Decision;
-import org.keycloak.authorization.model.Policy;
-import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.representations.idm.authorization.DecisionStrategy;
-
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
+
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
@@ -49,19 +49,21 @@ public abstract class DecisionResultCollector implements Decision<DefaultEvaluat
     @Override
     public void onComplete() {
         for (Result result : results.values()) {
+            int deniedCount = result.getResults().size();
+
             for (Result.PolicyResult policyResult : result.getResults()) {
                 if (isGranted(policyResult)) {
                     policyResult.setStatus(Effect.PERMIT);
+                    deniedCount--;
                 } else {
                     policyResult.setStatus(Effect.DENY);
                 }
             }
 
-            if (result.getResults().stream()
-                    .filter(policyResult -> Effect.DENY.equals(policyResult.getStatus())).count() > 0) {
-                result.setStatus(Effect.DENY);
-            } else {
+            if (deniedCount == 0) {
                 result.setStatus(Effect.PERMIT);
+            } else {
+                result.setStatus(Effect.DENY);
             }
         }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
index b74f845..094607a 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -85,7 +85,7 @@ public class DefaultPolicyEvaluator implements PolicyEvaluator {
             evaluatePolicies(() -> policyStore.findByScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()), consumer);
         }
 
-        if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && verified.get()) {
+        if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && !verified.get()) {
             createEvaluation(permission, executionContext, decision, null, null).grant();
         }
     }
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 ac6a97b..427978a 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
@@ -18,6 +18,17 @@
 
 package org.keycloak.authorization.admin.representation;
 
+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;
+
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.Decision.Effect;
 import org.keycloak.authorization.common.KeycloakIdentity;
@@ -33,17 +44,6 @@ import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
 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;
-
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
@@ -64,7 +64,7 @@ public class PolicyEvaluationResponse {
         AccessToken accessToken = identity.getAccessToken();
         AccessToken.Authorization authorizationData = new AccessToken.Authorization();
 
-        authorizationData.setPermissions(Permissions.allPermits(results, authorization, resourceServer));
+        authorizationData.setPermissions(Permissions.permits(results, authorization, resourceServer.getId()));
         accessToken.setAuthorization(authorizationData);
 
         response.rpt = accessToken;
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 894c3c7..620916c 100644
--- a/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
+++ b/services/src/main/java/org/keycloak/authorization/entitlement/EntitlementService.java
@@ -17,6 +17,30 @@
  */
 package org.keycloak.authorization.entitlement;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+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.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+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 javax.ws.rs.core.Response.Status;
+
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
@@ -50,29 +74,6 @@ import org.keycloak.services.ErrorResponse;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.resources.Cors;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.OPTIONS;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-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 javax.ws.rs.core.Response.Status;
-import java.util.ArrayList;
-import java.util.Arrays;
-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;
-
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
@@ -126,7 +127,7 @@ public class EntitlementService {
 
             @Override
             protected void onComplete(List<Result> results) {
-                List<Permission> entitlements = Permissions.allPermits(results, authorization, resourceServer);
+                List<Permission> entitlements = Permissions.permits(results, authorization, resourceServer.getId());
 
                 if (entitlements.isEmpty()) {
                     HashMap<Object, Object> error = new HashMap<>();
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 90de912..2e2c4a8 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -134,54 +134,82 @@ public final class Permissions {
         return permissions;
     }
 
-    public static List<Permission> allPermits(List<Result> evaluation, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
+    public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, String resourceServerId) {
         Map<String, Permission> 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();
+        for (Result result : evaluation) {
+            Set<Scope> deniedScopes = new HashSet<>();
+            Set<Scope> grantedScopes = new HashSet<>();
+            boolean resourceDenied = false;
+            ResourcePermission permission = result.getPermission();
+            List<Result.PolicyResult> results = result.getResults();
+            int deniedCount = results.size();
+
+            for (Result.PolicyResult policyResult : results) {
+                Policy policy = policyResult.getPolicy();
+                Set<Scope> policyScopes = policy.getScopes();
+
+                if (Effect.PERMIT.equals(policyResult.getStatus())) {
+                    if (isScopePermission(policy)) {
+                        // try to grant any scope from a scope-based permission
+                        grantedScopes.addAll(policyScopes);
+                    } else if (isResourcePermission(policy)) {
+                        // we assume that all requested scopes should be granted given that we are processing a resource-based permission.
+                        // Later they will be filtered based on any denied scope, if any.
+                        // TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
+                        grantedScopes.addAll(permission.getScopes());
+                    }
+                    deniedCount--;
+                } else {
+                    if (isScopePermission(policy)) {
+                        // store all scopes associated with the scope-based permission
+                        deniedScopes.addAll(policyScopes);
+                    } else if (isResourcePermission(policy)) {
+                        // we should not grant anything
+                        resourceDenied = true;
+                        break;
+                    }
+                }
+            }
 
-                    if ("scope".equals(policy.getType())) {
-                        Set<Resource> resources = policy.getResources();
+            if (!resourceDenied) {
+                // remove any scope denied from the list of granted scopes
+                if (!deniedScopes.isEmpty()) {
+                    grantedScopes.removeAll(deniedScopes);
+                }
 
-                        if (Effect.PERMIT.equals(result.getStatus())) {
-                            List<Scope> scopes = policy.getScopes().stream().collect(Collectors.toList());
+                // if there are no policy results is because the permission didn't match any policy.
+                // In this case, if results is empty is because we are in permissive mode.
+                if (!results.isEmpty()) {
+                    // update the current permission with the granted scopes
+                    permission.getScopes().clear();
+                    permission.getScopes().addAll(grantedScopes);
+                }
 
-                            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());
-                            }
-                        }
+                if (deniedCount == 0) {
+                    result.setStatus(Effect.PERMIT);
+                    grantPermission(authorizationProvider, permissions, permission, resourceServerId);
+                } else {
+                    // if a full deny or resource denied or the requested scopes were denied
+                    if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
+                        result.setStatus(Effect.DENY);
+                    } else {
+                        result.setStatus(Effect.PERMIT);
+                        grantPermission(authorizationProvider, permissions, permission, resourceServerId);
                     }
                 }
-                continue;
             }
-
-            grantPermission(authorizationProvider, permissions, permission, resourceServer.getId());
         }
 
         return permissions.values().stream().collect(Collectors.toList());
     }
 
-    public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, String resourceServer) {
-        Map<String, Permission> permissions = new HashMap<>();
-
-        for (Result evaluationResult : evaluation) {
-            ResourcePermission permission = evaluationResult.getPermission();
-
-            if (evaluationResult.getEffect().equals(Effect.DENY)) {
-                continue;
-            }
-
-            grantPermission(authorizationProvider, permissions, permission, resourceServer);
-        }
+    private static boolean isResourcePermission(Policy policy) {
+        return "resource".equals(policy.getType());
+    }
 
-        return permissions.values().stream().collect(Collectors.toList());
+    private static boolean isScopePermission(Policy policy) {
+        return "scope".equals(policy.getType());
     }
 
     private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, String resourceServer) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
index 2285be0..8f5018c 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java
@@ -32,6 +32,7 @@ import java.net.URL;
 
 import static org.keycloak.testsuite.util.WaitUtils.pause;
 import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
+import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -99,7 +100,8 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
     }
 
     public void logOut() {
-        signOutButton.click(); // Sometimes doesn't work in PhantomJS!
+        waitUntilElement(signOutButton); // Sometimes doesn't work in PhantomJS!
+        signOutButton.click();
         pause(WAIT_AFTER_OPERATION);
     }
     
@@ -113,7 +115,15 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
         pause(WAIT_AFTER_OPERATION);
     }
 
-    public void login(String username, String password, String... scopes) {
+    public void login(String username, String password, String... scopes) throws InterruptedException {
+        if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) {
+            Thread.sleep(2000);
+            logOut();
+            navigateTo();
+        }
+
+        Thread.sleep(2000);
+
         if (scopes.length > 0) {
             StringBuilder scopesValue = new StringBuilder();
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 332dd2f..2f9fede 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -143,7 +143,7 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
     }
 
     @Test
-    public void createAlbumWithInvalidUser() {
+    public void createAlbumWithInvalidUser() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
 
@@ -470,6 +470,7 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
 
             this.clientPage.createAlbum(resourceName);
 
+            this.clientPage.logOut();
             loginToClientPage("admin", "admin");
 
             this.clientPage.navigateToAdminAlbum();
@@ -629,7 +630,7 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
     
     //KEYCLOAK-3777
     @Test
-    public void testEntitlementRequest() {
+    public void testEntitlementRequest() throws Exception {
         try {
             this.deployer.deploy(RESOURCE_SERVER_ID);
             
@@ -663,13 +664,11 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
     }
 
     private void deleteAllCookiesForClientPage() {
-        clientPage.navigateTo();
         driver.manage().deleteAllCookies();
     }
-    
-    private void loginToClientPage(String username, String password, String... scopes) {
+
+    private void loginToClientPage(String username, String password, String... scopes) throws InterruptedException {
         // We need to log out by deleting cookies because the log out button sometimes doesn't work in PhantomJS
-        deleteAllCookiesForClientPage();
         deleteAllCookiesForTestRealm();
         clientPage.navigateTo();
         clientPage.login(username, password, scopes);
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 f6f7577..98ec9d9 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
@@ -1042,6 +1042,7 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
             };
 
             $scope.selectResource = function() {
+                $scope.policy.config.scopes = null;
                 if ($scope.policy.config.resources) {
                     ResourceServerResource.scopes({
                         realm: $route.current.params.realm,
@@ -1055,28 +1056,48 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
         },
 
         onInitUpdate : function(policy) {
-            policy.config.resources = eval(policy.config.resources);
-
-            if (policy.config.resources == null) {
-                policy.config.resources = [];
-            }
-
-            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.resources({
+                realm : $route.current.params.realm,
+                client : client.id,
+                id : policy.id
+            }, function(resources) {
+                if (resources.length > 0) {
+                    for (i = 0; i < resources.length; i++) {
+                        ResourceServerResource.get({
+                            realm: $route.current.params.realm,
+                            client: client.id,
+                            rsrid: resources[0]._id,
+                        }, function (resource) {
+                            ResourceServerResource.query({
+                                realm: $route.current.params.realm,
+                                client: client.id,
+                                _id: resource._id,
+                                deep: false
+                            }, function (resource) {
+                                resource[0].text = resource[0].name;
+                                $scope.policy.config.resources = resource[0];
+                                ResourceServerResource.scopes({
+                                    realm: $route.current.params.realm,
+                                    client: client.id,
+                                    rsrid: resource[0]._id
+                                }, function (scopes) {
+                                    $scope.policy.config.resources.scopes = scopes;
+                                });
+                                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 {
+                    $scope.policy.config.resources = null;
                     ResourceServerPolicy.scopes({
                         realm : $route.current.params.realm,
                         client : client.id,
@@ -1084,24 +1105,12 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
                     }, function(scopes) {
                         $scope.policy.config.scopes = [];
                         for (i = 0; i < scopes.length; i++) {
-                            $scope.policy.config.scopes.push(scopes[i].id);
+                            scopes[i].text = scopes[i].name;
+                            $scope.policy.config.scopes.push(scopes[i]);
                         }
                     });
-                });
-            } 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 = [];
             ResourceServerPolicy.associatedPolicies({
@@ -1119,12 +1128,14 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
         onUpdate : function() {
             if ($scope.policy.config.resources != null) {
                 $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]);
+            } else {
+                $scope.policy.config.resources = JSON.stringify([""]);
             }
 
             var scopes = [];
 
             for (i = 0; i < $scope.policy.config.scopes.length; i++) {
-                if ($scope.policy.config.resources == null) {
+                if ($scope.policy.config.scopes[i].id) {
                     scopes.push($scope.policy.config.scopes[i].id);
                 } else {
                     scopes.push($scope.policy.config.scopes[i]);
@@ -1237,8 +1248,12 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route,
                 $scope.selectedUsers.push(user);
             }
 
-            $scope.removeFromList = function(list, index) {
-                list.splice(index, 1);
+            $scope.removeFromList = function(list, user) {
+                for (i = 0; i < angular.copy(list).length; i++) {
+                    if (user == list[i]) {
+                        list.splice(i, 1);
+                    }
+                }
             }
         },
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html
index de85b71..2a3b019 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html
@@ -53,7 +53,7 @@
                             <tr ng-repeat="user in selectedUsers | orderBy:'username'">
                                 <td>{{user.username}}</td>
                                 <td class="kc-action-cell">
-                                    <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedUsers, $index);">{{:: 'remove' | translate}}</button>
+                                    <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedUsers, user);">{{:: 'remove' | translate}}</button>
                                 </td>
                             </tr>
                             <tr data-ng-show="!selectedUsers.length">