keycloak-memoizeit

Merge pull request #3072 from pedroigor/KEYCLOAK-3338 [KEYCLOAK-3338]

7/27/2016 6:14:25 PM

Changes

Details

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 af5f322..c8a99fe 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
@@ -23,9 +23,12 @@ import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.policy.evaluation.Evaluation;
 import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
+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;
 
 /**
@@ -47,23 +50,41 @@ public class RolePolicyProvider implements PolicyProvider {
 
     @Override
     public void evaluate(Evaluation evaluation) {
-        EvaluationContext context = evaluation.getContext();
-        String[] roleIds = getRoles(this.policy);
+        Map<String, Object>[] roleIds = getRoles(this.policy);
 
         if (roleIds.length > 0) {
-            Identity identity = context.getIdentity();
+            Identity identity = evaluation.getContext().getIdentity();
+
+            for (Map<String, Object> current : roleIds) {
+                RoleModel role = getCurrentRealm().getRoleById((String) current.get("id"));
 
-            for (String roleId : roleIds) {
-                RoleModel role = getCurrentRealm().getRoleById(roleId);
+                if (role != null) {
+                    boolean hasRole = hasRole(identity, role);
 
-                if (role != null && identity.hasRole(role.getName())) {
-                    evaluation.grant();
-                    break;
+                    if (!hasRole && Boolean.valueOf(isRequired(current))) {
+                        evaluation.deny();
+                        return;
+                    } else if (hasRole) {
+                        evaluation.grant();
+                    }
                 }
             }
         }
     }
 
+    private boolean isRequired(Map<String, Object> current) {
+        return (boolean) current.getOrDefault("required", false);
+    }
+
+    private boolean hasRole(Identity identity, RoleModel role) {
+        String roleName = role.getName();
+        if (role.isClientRole()) {
+            ClientModel clientModel = getCurrentRealm().getClientById(role.getContainerId());
+            return identity.hasClientRole(clientModel.getClientId(), roleName);
+        }
+        return identity.hasRealmRole(roleName);
+    }
+
     private RealmModel getCurrentRealm() {
         return this.authorization.getKeycloakSession().getContext().getRealm();
     }
@@ -72,4 +93,4 @@ public class RolePolicyProvider implements PolicyProvider {
     public void close() {
 
     }
-}
+}
\ No newline at end of file
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 598a41e..301bb7b 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
@@ -34,7 +34,9 @@ import org.keycloak.util.JsonSerialization;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -81,11 +83,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
                 RoleModel removedRole = ((RoleRemovedEvent) event).getRole();
 
                 policyStore.findByType(getId()).forEach(policy -> {
-                    List<String> roles = new ArrayList<>();
-
-                    for (String roleId : getRoles(policy)) {
-                        if (!roleId.equals(removedRole.getId())) {
-                            roles.add(roleId);
+                    List<Map> roles = new ArrayList<>();
+
+                    for (Map<String,Object> 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);
                         }
                     }
 
@@ -96,7 +104,9 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
                             });
                             policyStore.delete(policy.getId());
                         } else {
-                            policy.getConfig().put("roles", JsonSerialization.writeValueAsString(roles));
+                            Map<String, String> 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);
@@ -116,17 +126,17 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory {
         return "role";
     }
 
-    static String[] getRoles(Policy policy) {
+    static Map<String, Object>[] getRoles(Policy policy) {
         String roles = policy.getConfig().get("roles");
 
         if (roles != null) {
             try {
-                return JsonSerialization.readValue(roles.getBytes(), String[].class);
+                return JsonSerialization.readValue(roles.getBytes(), Map[].class);
             } catch (IOException e) {
                 throw new RuntimeException("Could not parse roles [" + roles + "] from policy config [" + policy.getName() + ".", e);
             }
         }
 
-        return new String[]{};
+        return new Map[] {};
     }
 }
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 6ccac33..2776b0a 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,6 +16,7 @@
  */
 package org.keycloak.representations.idm.authorization;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 import java.net.URI;
@@ -39,12 +40,14 @@ public class ResourceRepresentation {
     private String name;
     private String uri;
     private String type;
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private Set<ScopeRepresentation> scopes;
 
     @JsonProperty("icon_uri")
     private String iconUri;
     private ResourceOwnerRepresentation owner;
 
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private List<PolicyRepresentation> policies;
 
     /**
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
index 39aa9c7..3a1f252 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ScopeRepresentation.java
@@ -34,6 +34,7 @@ public class ScopeRepresentation {
     private String name;
     private String iconUri;
     private List<PolicyRepresentation> policies;
+    private List<ResourceRepresentation> resources;
 
     /**
      * Creates an instance.
@@ -94,6 +95,14 @@ public class ScopeRepresentation {
         this.policies = policies;
     }
 
+    public List<ResourceRepresentation> getResources() {
+        return this.resources;
+    }
+
+    public void setResources(List<ResourceRepresentation> resources) {
+        this.resources = resources;
+    }
+
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
index e9b470a..9614ca3 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
@@ -32,6 +32,8 @@ public class RoleRepresentation {
     protected Boolean scopeParamRequired;
     protected boolean composite;
     protected Composites composites;
+    private Boolean clientRole;
+    private String containerId;
 
     public static class Composites {
         protected Set<String> realm;
@@ -122,4 +124,20 @@ public class RoleRepresentation {
     public void setComposite(boolean composite) {
         this.composite = composite;
     }
+
+    public Boolean getClientRole() {
+        return clientRole;
+    }
+
+    public void setClientRole(Boolean clientRole) {
+        this.clientRole = clientRole;
+    }
+
+    public String getContainerId() {
+        return containerId;
+    }
+
+    public void setContainerId(String containerId) {
+        this.containerId = containerId;
+    }
 }
diff --git a/examples/authz/hello-world-authz-service/hello-world-authz-service.json b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
index ea56e62..6e509c7 100644
--- a/examples/authz/hello-world-authz-service/hello-world-authz-service.json
+++ b/examples/authz/hello-world-authz-service/hello-world-authz-service.json
@@ -1,4 +1,6 @@
 {
+  "allowRemoteResourceManagement": false,
+  "policyEnforcementMode": "ENFORCING",
   "resources": [
     {
       "name": "Default Resource",
@@ -8,22 +10,26 @@
   ],
   "policies": [
     {
-      "name": "Only From Realm Policy",
+      "name": "Default Policy",
       "description": "A policy that grants access only for users within this realm",
       "type": "js",
+      "logic": "POSITIVE",
+      "decisionStrategy": "AFFIRMATIVE",
       "config": {
         "applyPolicies": "[]",
-        "code": "var context = $evaluation.getContext();\n\n// using attributes from the evaluation context to obtain the realm\nvar contextAttributes = context.getAttributes();\nvar realmName = contextAttributes.getValue('kc.realm.name').asString(0);\n\n// using attributes from the identity to obtain the issuer\nvar identity = context.getIdentity();\nvar identityAttributes = identity.getAttributes();\nvar issuer = identityAttributes.getValue('iss').asString(0);\n\n// only users from the realm have access granted \nif (issuer.endsWith(realmName)) {\n    $evaluation.grant();\n}"
+        "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n"
       }
     },
     {
       "name": "Default Permission",
       "description": "A permission that applies to the default resource type",
       "type": "resource",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "defaultResourceType": "urn:hello-world-authz-service:resources:default",
         "default": "true",
-        "applyPolicies": "[\"Only From Realm Policy\"]"
+        "applyPolicies": "[\"Default Policy\"]"
       }
     }
   ]
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
index 98f0856..203b6e2 100755
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/index.html
@@ -19,7 +19,7 @@
 
 <body data-ng-controller="TokenCtrl">
 
-<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
+<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
 
 <div id="content-area" class="col-md-9" role="main">
     <div id="content" ng-view/>
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 2990675..e58c5f5 100755
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
     $scope.requestEntitlements = function () {
         Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
     }
+
+    $scope.Identity = Identity;
 });
 
 module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
     $scope.profile = Profile.get();
 });
 
-module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
+module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
     $scope.albums = {};
     $http.get(apiUrl + '/admin/album').success(function (data) {
         $scope.albums = data;
     });
     $scope.deleteAlbum = function (album) {
-        var newAlbum = new Album(album);
-        newAlbum.$delete({id: album.id}, function () {
+        new Album(album).$delete({id: album.id}, function () {
             $route.reload();
         });
     }
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
index da78224..c0cc6e1 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
@@ -10,7 +10,7 @@
             <td>
                 <ul>
                     <li data-ng-repeat="p in value">
-                        <a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
+                        <a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
                     </li>
                 </ul>
             </td>
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index bd5853e..78c252a 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -1,4 +1,4 @@
-<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
+<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
 <div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
 <hr/>
 <br/>
@@ -15,7 +15,7 @@
     </thead>
     <tbody>
         <tr data-ng-repeat="p in albums">
-            <td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
+            <td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
         </tr>
     </tbody>
 </table>
diff --git a/examples/authz/photoz/photoz-realm.json b/examples/authz/photoz/photoz-realm.json
index b3b2b81..baa8f66 100644
--- a/examples/authz/photoz/photoz-realm.json
+++ b/examples/authz/photoz/photoz-realm.json
@@ -53,7 +53,7 @@
         }
       ],
       "realmRoles": [
-        "user", "admin", "uma_authorization"
+        "admin", "uma_authorization"
       ],
       "clientRoles": {
         "realm-management": [
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 388c9e4..c28bca3 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -109,13 +109,7 @@ public class AlbumService {
 
     private void createProtectedResource(Album album) {
         try {
-            HashSet<ScopeRepresentation> scopes = new HashSet<>();
-
-            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
-            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_CREATE));
-            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
-
-            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
+            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
 
             albumResource.setOwner(album.getUserId());
 
diff --git a/examples/authz/photoz/photoz-restful-api-authz-service.json b/examples/authz/photoz/photoz-restful-api-authz-service.json
index e8d8862..6c786e7 100644
--- a/examples/authz/photoz/photoz-restful-api-authz-service.json
+++ b/examples/authz/photoz/photoz-restful-api-authz-service.json
@@ -21,10 +21,10 @@
           "name": "urn:photoz.com:scopes:album:view"
         },
         {
-          "name": "urn:photoz.com:scopes:album:create"
+          "name": "urn:photoz.com:scopes:album:delete"
         },
         {
-          "name": "urn:photoz.com:scopes:album:delete"
+          "name": "urn:photoz.com:scopes:album:create"
         }
       ]
     },
@@ -44,12 +44,15 @@
       "name": "Only Owner Policy",
       "description": "Defines that only the resource owner is allowed to do something",
       "type": "drools",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "mavenArtifactVersion": "2.1.0-SNAPSHOT",
         "mavenArtifactId": "photoz-authz-policy",
         "sessionName": "MainOwnerSession",
         "mavenArtifactGroupId": "org.keycloak",
         "moduleName": "PhotozAuthzOwnerPolicy",
+        "applyPolicies": "[]",
         "scannerPeriod": "1",
         "scannerPeriodUnit": "Hours"
       }
@@ -58,16 +61,22 @@
       "name": "Any Admin Policy",
       "description": "Defines that adminsitrators can do something",
       "type": "role",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
-        "roles": "[\"admin\"]"
+        "applyPolicies": "[]",
+        "roles": "[{\"id\":\"admin\",\"required\":true}]"
       }
     },
     {
       "name": "Any User Policy",
       "description": "Defines that any user can do something",
       "type": "role",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
-        "roles": "[\"user\"]"
+        "applyPolicies": "[]",
+        "roles": "[{\"id\":\"user\"}]"
       }
     },
     {
@@ -77,6 +86,7 @@
       "logic": "POSITIVE",
       "decisionStrategy": "UNANIMOUS",
       "config": {
+        "applyPolicies": "[]",
         "code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n    $evaluation.grant();\n}"
       }
     },
@@ -84,6 +94,8 @@
       "name": "Administration Policy",
       "description": "Defines that only administrators from a specific network address can do something.",
       "type": "aggregate",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
       }
@@ -92,35 +104,28 @@
       "name": "Only Owner and Administrators Policy",
       "description": "Defines that only the resource owner and administrators can do something",
       "type": "aggregate",
+      "logic": "POSITIVE",
       "decisionStrategy": "AFFIRMATIVE",
       "config": {
-        "applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
+        "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
       }
     },
     {
       "name": "Only From @keycloak.org or Admin",
       "description": "Defines that only users from @keycloak.org",
       "type": "js",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "applyPolicies": "[]",
         "code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n    $evaluation.grant();\n}"
       }
     },
     {
-      "name": "Only in the Period",
-      "description": "Access granted only during the morning",
-      "type": "time",
-      "config": {
-        "noa": "2016-01-03 23:59:59",
-        "expirationUnit": "Minutes",
-        "nbf": "2016-01-01 00:00:00",
-        "expirationTime": "1"
-      }
-    },
-    {
       "name": "Album Resource Permission",
       "description": "General policies that apply to all album resources.",
       "type": "resource",
+      "logic": "POSITIVE",
       "decisionStrategy": "AFFIRMATIVE",
       "config": {
         "defaultResourceType": "http://photoz.com/album",
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 6bdf96f..6e3271b 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
@@ -38,6 +38,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -99,7 +100,7 @@ public class CachedPolicyStore implements PolicyStore {
 
     @Override
     public List<Policy> findByResourceServer(String resourceServerId) {
-        return getDelegate().findByResourceServer(resourceServerId);
+        return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
     }
 
     @Override
@@ -179,12 +180,12 @@ public class CachedPolicyStore implements PolicyStore {
 
     @Override
     public List<Policy> findByType(String type) {
-        return getDelegate().findByType(type);
+        return getDelegate().findByType(type).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
     }
 
     @Override
     public List<Policy> findDependentPolicies(String id) {
-        return getDelegate().findDependentPolicies(id);
+        return getDelegate().findDependentPolicies(id).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
     }
 
     private String getCacheKeyForPolicy(String policyId) {
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 ee638f6..495df61 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
@@ -68,7 +68,21 @@ public class CachedResourceStore implements ResourceStore {
 
     @Override
     public void delete(String id) {
-        this.cache.remove(getCacheKeyForResource(id));
+        List<CachedResource> removed = this.cache.remove(getCacheKeyForResource(id));
+
+        if (removed != null) {
+            CachedResource cachedResource = removed.get(0);
+            List<String> byOwner = this.cache.get(getResourceOwnerCacheKey(cachedResource.getOwner()));
+
+            if (byOwner != null) {
+                byOwner.remove(id);
+
+                if (byOwner.isEmpty()) {
+                    this.cache.remove(getResourceOwnerCacheKey(cachedResource.getOwner()));
+                }
+            }
+        }
+
         getDelegate().delete(id);
     }
 
@@ -109,12 +123,12 @@ public class CachedResourceStore implements ResourceStore {
 
     @Override
     public List<Resource> findByResourceServer(String resourceServerId) {
-        return getDelegate().findByResourceServer(resourceServerId);
+        return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
     }
 
     @Override
     public List<Resource> findByScope(String... id) {
-        return getDelegate().findByScope(id);
+        return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList());
     }
 
     @Override
@@ -283,7 +297,7 @@ public class CachedResourceStore implements ResourceStore {
                 return;
             }
             cached = new ArrayList<>();
-            this.cache.put(getResourceOwnerCacheKey(resource.getOwner()), cached);
+            this.cache.put(cacheKey, cached);
         }
 
         if (cached != null && !cached.contains(resource.getId())) {
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 802989c..986d007 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
@@ -98,7 +98,7 @@ public class JPAResourceStore implements ResourceStore {
 
     @Override
     public List<Resource> findByScope(String... id) {
-        Query query = entityManager.createQuery("from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
+        Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)");
 
         query.setParameter("scopeIds", Arrays.asList(id));
 
diff --git a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
index ce2bc51..a70d3c0 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/attribute/Attributes.java
@@ -95,7 +95,7 @@ public interface Attributes {
         private final String[] values;
         private final String name;
 
-        Entry(String name, Collection<String> values) {
+        public Entry(String name, Collection<String> values) {
             this.name = name;
             this.values = values.toArray(new String[values.size()]);
         }
diff --git a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
index ebc9679..f16e6c3 100644
--- a/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
+++ b/server-spi/src/main/java/org/keycloak/authorization/identity/Identity.java
@@ -19,6 +19,10 @@ package org.keycloak.authorization.identity;
 
 import org.keycloak.authorization.attribute.Attributes;
 
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.Predicate;
+
 /**
  * <p>Represents a security identity, which can be a person or non-person entity that was previously authenticated.
  *
@@ -45,13 +49,53 @@ public interface Identity {
     Attributes getAttributes();
 
     /**
-     * Indicates if this identity is granted with a role with the given <code>roleName</code>.
+     * Indicates if this identity is granted with a role (realm or client) with the given <code>roleName</code>.
      *
      * @param roleName the name of the role
      *
      * @return true if the identity has the given role. Otherwise, it returns false.
      */
     default boolean hasRole(String roleName) {
-        return getAttributes().containsValue("roles", roleName);
+        return hasRealmRole(roleName) || hasClientRole(roleName);
+    }
+
+    /**
+     * Indicates if this identity is granted with a realm role with the given <code>roleName</code>.
+     *
+     * @param roleName the name of the role
+     *
+     * @return true if the identity has the given role. Otherwise, it returns false.
+     */
+    default boolean hasRealmRole(String roleName) {
+        return getAttributes().containsValue("kc.realm.roles", roleName);
+    }
+
+    /**
+     * Indicates if this identity is granted with a client role with the given <code>roleName</code>.
+     *
+     * @param clientId the client id
+     * @param roleName the name of the role
+     *
+     * @return true if the identity has the given role. Otherwise, it returns false.
+     */
+    default boolean hasClientRole(String clientId, String roleName) {
+        return getAttributes().containsValue("kc.client." + clientId + ".roles", roleName);
+    }
+
+    /**
+     * Indicates if this identity is granted with a client role with the given <code>roleName</code>.
+     *
+     * @param roleName the name of the role
+     *
+     * @return true if the identity has the given role. Otherwise, it returns false.
+     */
+    default boolean hasClientRole(String roleName) {
+        return getAttributes().toMap().entrySet().stream().filter(entry -> {
+            String key = entry.getKey();
+            if (key.startsWith("kc.client") && key.endsWith(".roles")) {
+                return getAttributes().containsValue(key, roleName);
+            }
+            return false;
+        }).findFirst().isPresent();
     }
 }
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
index e5230f9..8ff3664 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
@@ -17,11 +17,22 @@
 
 package org.keycloak.migration.migrators;
 
+import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.migration.ModelVersion;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.util.JsonSerialization;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  *
@@ -33,6 +44,7 @@ public class MigrateTo2_1_0 {
     public void migrate(KeycloakSession session) {
         for (RealmModel realm : session.realms().getRealms()) {
             migrateDefaultRequiredAction(realm);
+            migrateRolePolicies(realm, session);
         }
     }
     
@@ -46,4 +58,46 @@ public class MigrateTo2_1_0 {
 
         otpAction.setName("Configure OTP");
     }
+
+    // KEYCLOAK-3338: Changes to how role policy config is stored"
+    private void migrateRolePolicies(RealmModel realm, KeycloakSession session) {
+        AuthorizationProvider authorizationProvider = session.getProvider(AuthorizationProvider.class);
+        StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+        PolicyStore policyStore = storeFactory.getPolicyStore();
+        realm.getClients().forEach(clientModel -> {
+            ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(clientModel.getId());
+
+            if (resourceServer != null) {
+                policyStore.findByType("role").forEach(policy -> {
+                    Map<String, String> config = policy.getConfig();
+                    String roles = config.get("roles");
+                    List roleConfig;
+
+                    try {
+                        roleConfig = JsonSerialization.readValue(roles, List.class);
+                    } catch (Exception e) {
+                        throw new RuntimeException("Malformed configuration for role policy [" + policy.getName() + "].", e);
+                    }
+
+                    if (!roleConfig.isEmpty() && roleConfig.get(0) instanceof String) {
+                        try {
+                            config.put("roles", JsonSerialization.writeValueAsString(roleConfig.stream().map(new Function<String, Map>() {
+                                @Override
+                                public Map apply(String roleId) {
+                                    Map updated = new HashMap();
+
+                                    updated.put("id", roleId);
+
+                                    return updated;
+                                }
+                            }).collect(Collectors.toList())));
+                            policy.setConfig(config);
+                        } catch (Exception e) {
+                            throw new RuntimeException("Failed to migrate role policy [" + policy.getName() + "].", e);
+                        }
+                    }
+                });
+            }
+        });
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 7f7f7e0..203736f 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -218,6 +218,8 @@ public class ModelToRepresentation {
         rep.setDescription(role.getDescription());
         rep.setScopeParamRequired(role.isScopeParamRequired());
         rep.setComposite(role.isComposite());
+        rep.setClientRole(role.isClientRole());
+        rep.setContainerId(role.getContainerId());
         return rep;
     }
 
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 f4d8feb..8e4ec5e 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -136,11 +136,19 @@ public class PolicyEvaluationService {
     }
 
     private List<ResourcePermission> createPermissions(PolicyEvaluationRequest representation, EvaluationContext evaluationContext, AuthorizationProvider authorization) {
-        if (representation.isEntitlements()) {
+        List<PolicyEvaluationRequest.Resource> resources = representation.getResources();
+
+        for (PolicyEvaluationRequest.Resource resource : new ArrayList<>(resources)) {
+            if (resource.getId() == null && (resource.getScopes() == null || resource.getScopes().isEmpty())) {
+                resources.remove(resource);
+            }
+        }
+
+        if (representation.isEntitlements() || resources.isEmpty()) {
             return Permissions.all(this.resourceServer, evaluationContext.getIdentity(), authorization);
         }
 
-        return representation.getResources().stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
+        return resources.stream().flatMap((Function<PolicyEvaluationRequest.Resource, Stream<ResourcePermission>>) resource -> {
             Set<String> givenScopes = resource.getScopes();
 
             if (givenScopes == null) {
@@ -157,7 +165,13 @@ public class PolicyEvaluationService {
             } else if (resource.getType() != null) {
                 return storeFactory.getResourceStore().findByType(resource.getType()).stream().map(resource1 -> new ResourcePermission(resource1, scopes, resourceServer));
             } else {
-                return scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer));
+                List<ResourcePermission> collect = scopes.stream().map(scope -> new ResourcePermission(null, asList(scope), resourceServer)).collect(Collectors.toList());
+
+                for (Scope scope : scopes) {
+                    collect.addAll(storeFactory.getResourceStore().findByScope(scope.getId()).stream().map(resource12 -> new ResourcePermission(resource12, asList(scope), 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 d2d623c..7c529e9 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -154,7 +154,11 @@ public class PolicyService {
         }
 
         policyStore.findDependentPolicies(id).forEach(dependentPolicy -> {
-            dependentPolicy.removeAssociatedPolicy(policy);
+            if (dependentPolicy.getAssociatedPolicies().size() == 1) {
+                policyStore.delete(dependentPolicy.getId());
+            } else {
+                dependentPolicy.removeAssociatedPolicy(policy);
+            }
         });
 
         policyStore.delete(policy.getId());
@@ -271,7 +275,6 @@ public class PolicyService {
             }
 
             StoreFactory storeFactory = authorization.getStoreFactory();
-            PolicyStore policyStore = storeFactory.getPolicyStore();
 
             for (String scopeId : scopeIds) {
                 boolean hasScope = false;
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 77fe01e..9a3c9b3 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
@@ -21,14 +21,10 @@ package org.keycloak.authorization.admin.representation;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.Decision.Effect;
 import org.keycloak.authorization.admin.util.Models;
-import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.policy.evaluation.Result;
 import org.keycloak.authorization.policy.evaluation.Result.PolicyResult;
-import org.keycloak.authorization.store.StoreFactory;
-import org.keycloak.authorization.util.Permissions;
-import org.keycloak.representations.idm.authorization.Permission;
 import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@@ -90,9 +86,17 @@ public class PolicyEvaluationResponse {
                 policies.add(toRepresentation(policy, authorization));
             }
 
+            if (rep.getResource().getId() != null) {
+                if (!rep.getScopes().isEmpty()) {
+                    rep.getResource().setName(rep.getResource().getName() + " with scopes " + rep.getScopes().stream().map(ScopeRepresentation::getName).collect(Collectors.toList()));
+                }
+            }
+
             rep.setPolicies(policies);
         }
 
+        resultsRep.sort((o1, o2) -> o1.getResource().getName().compareTo(o2.getResource().getName()));
+
         response.results = resultsRep;
 
         return response;
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
index 59ba844..93c5ce8 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java
@@ -17,8 +17,6 @@
  */
 package org.keycloak.authorization.admin;
 
-import org.jboss.resteasy.plugins.providers.multipart.InputPart;
-import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.admin.util.Models;
@@ -146,7 +144,11 @@ public class ResourceServerService {
                 .stream().map(resource -> {
                     ResourceRepresentation rep = Models.toRepresentation(resource, resourceServer, authorization);
 
-                    rep.getOwner().setId(null);
+                    if (rep.getOwner().getId().equals(this.resourceServer.getClientId())) {
+                        rep.setOwner(null);
+                    } else {
+                        rep.getOwner().setId(null);
+                    }
                     rep.setId(null);
                     rep.setPolicies(null);
                     rep.getScopes().forEach(scopeRepresentation -> {
@@ -175,15 +177,8 @@ public class ResourceServerService {
             ScopeRepresentation rep = Models.toRepresentation(scope, authorization);
 
             rep.setId(null);
-
-            rep.getPolicies().forEach(policyRepresentation -> {
-                policyRepresentation.setId(null);
-                policyRepresentation.setConfig(null);
-                policyRepresentation.setType(null);
-                policyRepresentation.setDecisionStrategy(null);
-                policyRepresentation.setDescription(null);
-                policyRepresentation.setDependentPolicies(null);
-            });
+            rep.setPolicies(null);
+            rep.setResources(null);
 
             return rep;
         }).collect(Collectors.toList());
@@ -258,131 +253,74 @@ public class ResourceServerService {
             String roles = config.get("roles");
 
             if (roles != null && !roles.isEmpty()) {
-                roles = roles.replace("[", "");
-                roles = roles.replace("]", "");
-
-                if (!roles.isEmpty()) {
-                    String roleNames = "";
-
-                    for (String role : roles.split(",")) {
-                        if (!roleNames.isEmpty()) {
-                            roleNames = roleNames + ",";
-                        }
-
-                        role = role.replace("\"", "");
-
-                        roleNames = roleNames + "\"" + this.realm.getRole(role).getId() + "\"";
-                    }
-
-                    config.put("roles", "[" + roleNames + "]");
+                try {
+                    List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
+                    config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleConfig -> {
+                        roleConfig.put("id", realm.getRole(roleConfig.get("id").toString()).getId());
+                        return roleConfig;
+                    }).collect(Collectors.toList())));
+                } catch (Exception e) {
+                    throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
                 }
             }
 
             String users = config.get("users");
 
-            if (users != null) {
-                users = users.replace("[", "");
-                users = users.replace("]", "");
-
-                if (!users.isEmpty()) {
-                    String userNames = "";
-
-                    for (String user : users.split(",")) {
-                        if (!userNames.isEmpty()) {
-                            userNames =  userNames + ",";
-                        }
-
-                        user = user.replace("\"", "");
-
-                        userNames = userNames + "\"" + this.session.users().getUserByUsername(user, this.realm).getId() + "\"";
-                    }
-
-                    config.put("users", "[" + userNames + "]");
+            if (users != null && !users.isEmpty()) {
+                try {
+                    List<String> usersMap = JsonSerialization.readValue(users, List.class);
+                    config.put("users", JsonSerialization.writeValueAsString(usersMap.stream().map(userName -> this.session.users().getUserByUsername(userName, this.realm).getId()).collect(Collectors.toList())));
+                } catch (Exception e) {
+                    throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
                 }
             }
 
             String scopes = config.get("scopes");
 
             if (scopes != null && !scopes.isEmpty()) {
-                scopes = scopes.replace("[", "");
-                scopes = scopes.replace("]", "");
-
-                if (!scopes.isEmpty()) {
-                    String scopeNames = "";
-
-                    for (String scope : scopes.split(",")) {
-                        if (!scopeNames.isEmpty()) {
-                            scopeNames =  scopeNames + ",";
-                        }
-
-                        scope = scope.replace("\"", "");
-
-                        Scope newScope = scopeStore.findByName(scope, resourceServer.getId());
+                try {
+                    List<String> scopesMap = JsonSerialization.readValue(scopes, List.class);
+                    config.put("scopes", JsonSerialization.writeValueAsString(scopesMap.stream().map(scopeName -> {
+                        Scope newScope = scopeStore.findByName(scopeName, resourceServer.getId());
 
                         if (newScope == null) {
-                            throw new RuntimeException("Scope with name [" + scope + "] not defined.");
+                            throw new RuntimeException("Scope with name [" + scopeName + "] not defined.");
                         }
 
-                        scopeNames = scopeNames + "\"" + newScope.getId() + "\"";
-                    }
-
-                    config.put("scopes", "[" + scopeNames + "]");
+                        return newScope.getId();
+                    }).collect(Collectors.toList())));
+                } catch (Exception e) {
+                    throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
                 }
             }
 
             String policyResources = config.get("resources");
 
             if (policyResources != null && !policyResources.isEmpty()) {
-                policyResources = policyResources.replace("[", "");
-                policyResources = policyResources.replace("]", "");
-
-                if (!policyResources.isEmpty()) {
-                    String resourceNames = "";
-
-                    for (String resource : policyResources.split(",")) {
-                        if (!resourceNames.isEmpty()) {
-                            resourceNames =  resourceNames + ",";
-                        }
-
-                        resource = resource.replace("\"", "");
-
-                        if ("".equals(resource)) {
-                            continue;
-                        }
-
-                        resourceNames = resourceNames + "\"" + storeFactory.getResourceStore().findByName(resource, resourceServer.getId()).getId() + "\"";
-                    }
-
-                    config.put("resources", "[" + resourceNames + "]");
+                try {
+                    List<String> resources = JsonSerialization.readValue(policyResources, List.class);
+                    config.put("resources", JsonSerialization.writeValueAsString(resources.stream().map(resourceName -> storeFactory.getResourceStore().findByName(resourceName, resourceServer.getId()).getId()).collect(Collectors.toList())));
+                } catch (Exception e) {
+                    throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
                 }
             }
 
             String applyPolicies = config.get("applyPolicies");
 
             if (applyPolicies != null && !applyPolicies.isEmpty()) {
-                applyPolicies = applyPolicies.replace("[", "");
-                applyPolicies = applyPolicies.replace("]", "");
-
-                if (!applyPolicies.isEmpty()) {
-                    String policyNames = "";
-
-                    for (String pId : applyPolicies.split(",")) {
-                        if (!policyNames.isEmpty()) {
-                            policyNames = policyNames + ",";
-                        }
-
-                        pId = pId.replace("\"", "").trim();
-
-                        Policy policy = policyStore.findByName(pId, resourceServer.getId());
+                try {
+                    List<String> policies = JsonSerialization.readValue(applyPolicies, List.class);
+                    config.put("applyPolicies", JsonSerialization.writeValueAsString(policies.stream().map(policyName -> {
+                        Policy policy = policyStore.findByName(policyName, resourceServer.getId());
 
                         if (policy == null) {
-                            throw new RuntimeException("Policy with name [" + pId + "] not defined.");
+                            throw new RuntimeException("Policy with name [" + policyName + "] not defined.");
                         }
 
-                        policyNames = policyNames + "\"" + policy.getId() + "\"";
-                    }
-
-                    config.put("applyPolicies", "[" + policyNames + "]");
+                        return policy.getId();
+                    }).collect(Collectors.toList())));
+                } catch (Exception e) {
+                    throw new RuntimeException("Error while exporting policy [" + policyRepresentation.getName() + "].", e);
                 }
             }
 
@@ -491,123 +429,59 @@ public class ResourceServerService {
     }
 
     private PolicyRepresentation createPolicyRepresentation(StoreFactory storeFactory, Policy policy) {
-        PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
-
-        rep.setId(null);
-        rep.setDependentPolicies(null);
-
-        Map<String, String> config = rep.getConfig();
-
-        String roles = config.get("roles");
+        try {
+            PolicyRepresentation rep = Models.toRepresentation(policy, authorization);
 
-        if (roles != null && !roles.isEmpty()) {
-            roles = roles.replace("[", "");
-            roles = roles.replace("]", "");
-
-            if (!roles.isEmpty()) {
-                String roleNames = "";
-
-                for (String role : roles.split(",")) {
-                    if (!roleNames.isEmpty()) {
-                        roleNames = roleNames + ",";
-                    }
+            rep.setId(null);
+            rep.setDependentPolicies(null);
 
-                    role = role.replace("\"", "");
+            Map<String, String> config = rep.getConfig();
 
-                    roleNames = roleNames + "\"" + this.realm.getRoleById(role).getName() + "\"";
-                }
+            String roles = config.get("roles");
 
-                config.put("roles", "[" + roleNames + "]");
+            if (roles != null && !roles.isEmpty()) {
+                List<Map> rolesMap = JsonSerialization.readValue(roles, List.class);
+                config.put("roles", JsonSerialization.writeValueAsString(rolesMap.stream().map(roleMap -> {
+                    roleMap.put("id", realm.getRoleById(roleMap.get("id").toString()).getName());
+                    return roleMap;
+                }).collect(Collectors.toList())));
             }
-        }
-
-        String users = config.get("users");
 
-        if (users != null) {
-            users = users.replace("[", "");
-            users = users.replace("]", "");
+            String users = config.get("users");
 
-            if (!users.isEmpty()) {
+            if (users != null && !users.isEmpty()) {
                 UserFederationManager userManager = this.session.users();
-                String userNames = "";
-
-                for (String user : users.split(",")) {
-                    if (!userNames.isEmpty()) {
-                        userNames =  userNames + ",";
-                    }
-
-                    user = user.replace("\"", "");
-
-                    userNames = userNames + "\"" + userManager.getUserById(user, this.realm).getUsername() + "\"";
-                }
-
-                config.put("users", "[" + userNames + "]");
+                List<String> userIds = JsonSerialization.readValue(users, List.class);
+                config.put("users", JsonSerialization.writeValueAsString(userIds.stream().map(userId -> userManager.getUserById(userId, this.realm).getUsername()).collect(Collectors.toList())));
             }
-        }
-
-        String scopes = config.get("scopes");
 
-        if (scopes != null && !scopes.isEmpty()) {
-            scopes = scopes.replace("[", "");
-            scopes = scopes.replace("]", "");
+            String scopes = config.get("scopes");
 
-            if (!scopes.isEmpty()) {
+            if (scopes != null && !scopes.isEmpty()) {
                 ScopeStore scopeStore = storeFactory.getScopeStore();
-                String scopeNames = "";
-
-                for (String scope : scopes.split(",")) {
-                    if (!scopeNames.isEmpty()) {
-                        scopeNames =  scopeNames + ",";
-                    }
-
-                    scope = scope.replace("\"", "");
-
-                    scopeNames = scopeNames + "\"" + scopeStore.findById(scope).getName() + "\"";
-                }
-
-                config.put("scopes", "[" + scopeNames + "]");
+                List<String> scopeIds = JsonSerialization.readValue(scopes, List.class);
+                config.put("scopes", JsonSerialization.writeValueAsString(scopeIds.stream().map(scopeId -> scopeStore.findById(scopeId).getName()).collect(Collectors.toList())));
             }
-        }
 
-        String policyResources = config.get("resources");
-
-        if (policyResources != null && !policyResources.isEmpty()) {
-            policyResources = policyResources.replace("[", "");
-            policyResources = policyResources.replace("]", "");
+            String policyResources = config.get("resources");
 
-            if (!policyResources.isEmpty()) {
+            if (policyResources != null && !policyResources.isEmpty()) {
                 ResourceStore resourceStore = storeFactory.getResourceStore();
-                String resourceNames = "";
-
-                for (String resource : policyResources.split(",")) {
-                    if (!resourceNames.isEmpty()) {
-                        resourceNames =  resourceNames + ",";
-                    }
-
-                    resource = resource.replace("\"", "");
-
-                    resourceNames = resourceNames + "\"" + resourceStore.findById(resource).getName() + "\"";
-                }
-
-                config.put("resources", "[" + resourceNames + "]");
+                List<String> resourceIds = JsonSerialization.readValue(policyResources, List.class);
+                config.put("resources", JsonSerialization.writeValueAsString(resourceIds.stream().map(resourceId -> resourceStore.findById(resourceId).getName()).collect(Collectors.toList())));
             }
-        }
 
-        String policyNames = "";
-        Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
+            Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
 
-        if (!associatedPolicies.isEmpty()) {
-            for (Policy associatedPolicy : associatedPolicies) {
-                if (!policyNames.isEmpty()) {
-                    policyNames = policyNames + ",";
-                }
-
-                policyNames = policyNames + "\"" + associatedPolicy.getName() + "\"";
+            if (!associatedPolicies.isEmpty()) {
+                config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList())));
             }
 
-            config.put("applyPolicies", "[" + policyNames + "]");
-        }
+            rep.setAssociatedPolicies(null);
 
-        return rep;
+            return rep;
+        } catch (Exception e) {
+            throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e);
+        }
     }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
index 60dc51e..7e9d66d 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/util/Models.java
@@ -63,13 +63,18 @@ public final class Models {
         scope.setId(model.getId());
         scope.setName(model.getName());
         scope.setIconUri(model.getIconUri());
-        scope.setPolicies(new ArrayList<>());
 
-        Set<Policy> policies = new HashSet<>();
+        StoreFactory storeFactory = authorizationProvider.getStoreFactory();
 
-        policies.addAll(authorizationProvider.getStoreFactory().getPolicyStore().findByScopeIds(Arrays.asList(model.getId()), model.getResourceServer().getId()));
+        scope.setResources(new ArrayList<>());
 
-        for (Policy policyModel : policies) {
+        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());
@@ -79,7 +84,7 @@ public final class Models {
             if (!scope.getPolicies().contains(policy)) {
                 scope.getPolicies().add(policy);
             }
-        }
+        });
 
         return scope;
     }
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 5ddd431..c40c38a 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.saml.common.util.StringUtil;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.util.JsonSerialization;
 
@@ -37,6 +38,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -53,61 +55,59 @@ public class KeycloakIdentity implements Identity {
     }
 
     public KeycloakIdentity(AccessToken accessToken, KeycloakSession keycloakSession) {
-        this.accessToken = accessToken;
-
-        if (this.accessToken == null) {
+        if (accessToken == null) {
             throw new ErrorResponseException("invalid_bearer_token", "Could not obtain bearer access_token from request.", Status.FORBIDDEN);
         }
-
+        if (keycloakSession == null) {
+            throw new ErrorResponseException("no_keycloak_session", "No keycloak session", Status.FORBIDDEN);
+        }
+        this.accessToken = accessToken;
         this.keycloakSession = keycloakSession;
         this.realm = keycloakSession.getContext().getRealm();
 
-        HashMap<String, Collection<String>> attributes = new HashMap<>();
+        Map<String, Collection<String>> attributes = new HashMap<>();
 
         try {
             ObjectNode objectNode = JsonSerialization.createObjectNode(this.accessToken);
             Iterator<String> iterator = objectNode.fieldNames();
-            List<String> roleNames = new ArrayList<>();
 
             while (iterator.hasNext()) {
                 String fieldName = iterator.next();
                 JsonNode fieldValue = objectNode.get(fieldName);
                 List<String> values = new ArrayList<>();
 
-                values.add(fieldValue.asText());
-
-                if (fieldName.equals("realm_access")) {
-                    JsonNode grantedRoles = fieldValue.get("roles");
+                if (fieldValue.isArray()) {
+                    Iterator<JsonNode> valueIterator = fieldValue.iterator();
 
-                    if (grantedRoles != null) {
-                        Iterator<JsonNode> rolesIt = grantedRoles.iterator();
+                    while (valueIterator.hasNext()) {
+                        values.add(valueIterator.next().asText());
+                    }
+                } else {
+                    String value = fieldValue.asText();
 
-                        while (rolesIt.hasNext()) {
-                            roleNames.add(rolesIt.next().asText());
-                        }
+                    if (StringUtil.isNullOrEmpty(value)) {
+                        continue;
                     }
+
+                    values.add(value);
                 }
 
-                if (fieldName.equals("resource_access")) {
-                    Iterator<JsonNode> resourceAccessIt = fieldValue.iterator();
+                if (!values.isEmpty()) {
+                    attributes.put(fieldName, values);
+                }
+            }
 
-                    while (resourceAccessIt.hasNext()) {
-                        JsonNode grantedRoles = resourceAccessIt.next().get("roles");
+            AccessToken.Access realmAccess = accessToken.getRealmAccess();
 
-                        if (grantedRoles != null) {
-                            Iterator<JsonNode> rolesIt = grantedRoles.iterator();
+            if (realmAccess != null) {
+                attributes.put("kc.realm.roles", realmAccess.getRoles());
+            }
 
-                            while (rolesIt.hasNext()) {
-                                roleNames.add(rolesIt.next().asText());
-                            }
-                        }
-                    }
-                }
+            Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
 
-                attributes.put(fieldName, values);
+            if (resourceAccess != null) {
+                resourceAccess.forEach((clientId, access) -> attributes.put("kc.client." + clientId + ".roles", access.getRoles()));
             }
-
-            attributes.put("roles", roleNames);
         } catch (Exception e) {
             throw new RuntimeException("Error while reading attributes from security token.", e);
         }
diff --git a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
index f3695d0..45fb6fe 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/ProtectionService.java
@@ -28,6 +28,7 @@ import org.keycloak.authorization.protection.permission.PermissionService;
 import org.keycloak.authorization.protection.permission.PermissionsService;
 import org.keycloak.authorization.protection.resource.ResourceService;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.ErrorResponseException;
 
@@ -48,16 +49,12 @@ public class ProtectionService {
     @Path("/resource_set")
     public Object resource() {
         KeycloakIdentity identity = createIdentity();
-
-        if (!identity.hasRole("uma_protection")) {
-            throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
-        }
-
-        ResourceSetService resourceManager = new ResourceSetService(getResourceServer(identity), this.authorization, null);
+        ResourceServer resourceServer = getResourceServer(identity);
+        ResourceSetService resourceManager = new ResourceSetService(resourceServer, this.authorization, null);
 
         ResteasyProviderFactory.getInstance().injectProperties(resourceManager);
 
-        ResourceService resource = new ResourceService(getResourceServer(identity), identity, resourceManager, this.authorization);
+        ResourceService resource = new ResourceService(resourceServer, identity, resourceManager, this.authorization);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
 
@@ -68,10 +65,6 @@ public class ProtectionService {
     public Object permission() {
         KeycloakIdentity identity = createIdentity();
 
-        if (!identity.hasRole("uma_protection")) {
-            throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
-        }
-
         PermissionService resource = new PermissionService(identity, getResourceServer(identity), this.authorization);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -83,10 +76,6 @@ public class ProtectionService {
     public Object permissions() {
         KeycloakIdentity identity = createIdentity();
 
-        if (!identity.hasRole("uma_protection")) {
-            throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
-        }
-
         PermissionsService resource = new PermissionsService(identity, getResourceServer(identity), this.authorization);
 
         ResteasyProviderFactory.getInstance().injectProperties(resource);
@@ -95,7 +84,17 @@ public class ProtectionService {
     }
 
     private KeycloakIdentity createIdentity() {
-        return new KeycloakIdentity(this.authorization.getKeycloakSession());
+        KeycloakIdentity identity = new KeycloakIdentity(this.authorization.getKeycloakSession());
+        ResourceServer resourceServer = getResourceServer(identity);
+        KeycloakSession keycloakSession = authorization.getKeycloakSession();
+        RealmModel realm = keycloakSession.getContext().getRealm();
+        ClientModel client = realm.getClientById(resourceServer.getClientId());
+
+        if (!identity.hasClientRole(client.getClientId(), "uma_protection")) {
+            throw new ErrorResponseException(OAuthErrorException.INVALID_SCOPE, "Requires uma_protection scope.", Status.FORBIDDEN);
+        }
+
+        return identity;
     }
 
     private ResourceServer getResourceServer(Identity identity) {
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 f2cce51..ed49697 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -61,20 +61,38 @@ public final class Permissions {
         StoreFactory storeFactory = authorization.getStoreFactory();
         ResourceStore resourceStore = storeFactory.getResourceStore();
 
-        resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
-        resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource)));
+        resourceStore.findByOwner(resourceServer.getClientId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
+        resourceStore.findByOwner(identity.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissions(resource, resourceServer, authorization)));
 
         return permissions;
     }
 
-    public static List<ResourcePermission> createResourcePermissions(Resource resource) {
+    public static List<ResourcePermission> createResourcePermissions(Resource resource, ResourceServer resourceServer, AuthorizationProvider authorization) {
         List<ResourcePermission> permissions = new ArrayList<>();
         List<Scope> scopes = resource.getScopes();
 
-        permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
+        if (scopes.isEmpty()) {
+            String type = resource.getType();
+
+            // check if there is a typed resource whose scopes are inherited by the resource being requested. In this case, we assume that parent resource
+            // is owned by the resource server itself
+            if (type != null) {
+                StoreFactory storeFactory = authorization.getStoreFactory();
+                ResourceStore resourceStore = storeFactory.getResourceStore();
+                resourceStore.findByType(type).forEach(resource1 -> {
+                    if (resource1.getOwner().equals(resourceServer.getClientId())) {
+                        scopes.addAll(resource1.getScopes());
+                    }
+                });
+            }
+        }
 
-        for (Scope scope : scopes) {
-            permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
+        if (scopes.size() > 1) {
+            for (Scope scope : scopes) {
+                permissions.add(new ResourcePermission(resource, Arrays.asList(scope), resource.getResourceServer()));
+            }
+        } else {
+            permissions.add(new ResourcePermission(resource, Collections.emptyList(), resource.getResourceServer()));
         }
 
         return permissions;
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 31b221b..1faf21e 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
@@ -49,6 +49,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import java.io.IOException;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -56,6 +57,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static org.jboss.aesh.terminal.Key.e;
+
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
@@ -279,8 +282,12 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest 
             RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
             RoleModel adminRole = realm.getRole("admin");
 
+            Map role = new HashMap();
+
+            role.put("id", adminRole.getId());
+
             try {
-                config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
+                config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
@@ -352,10 +359,14 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest 
             Policy policy = policyStore.create("Any User Policy", "role", resourceServer);
             HashedMap config = new HashedMap();
             RealmModel realm = authorizationProvider.getKeycloakSession().realms().getRealmByName(TEST_REALM_NAME);
-            RoleModel adminRole = realm.getRole("user");
+            RoleModel userRole = realm.getRole("user");
+
+            Map role = new HashMap();
+
+            role.put("id", userRole.getId());
 
             try {
-                config.put("roles", JsonSerialization.writeValueAsString(new String[] {adminRole.getId()}));
+                config.put("roles", JsonSerialization.writeValueAsString(new Map[] {role}));
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
index 5ac24ea..bec9fae 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/index.html
@@ -19,7 +19,7 @@
 
 <body data-ng-controller="TokenCtrl">
 
-<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a>
+<a href data-ng-click="showRpt()">Show Requesting Party Token </a> | <a href data-ng-click="showAccessToken()">Show Access Token </a> | <a href data-ng-click="requestEntitlements()">Request Entitlements</a> | <a href="" ng-click="Identity.logout()">Sign Out</a>
 
 <div id="content-area" class="col-md-9" role="main">
     <div id="content" ng-view/>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
index 2990675..e58c5f5 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js
@@ -64,6 +64,8 @@ module.controller('TokenCtrl', function ($scope, Identity) {
     $scope.requestEntitlements = function () {
         Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {});
     }
+
+    $scope.Identity = Identity;
 });
 
 module.controller('AlbumCtrl', function ($scope, $http, $routeParams, $location, Album) {
@@ -83,14 +85,13 @@ module.controller('ProfileCtrl', function ($scope, $http, $routeParams, $locatio
     $scope.profile = Profile.get();
 });
 
-module.controller('AdminAlbumCtrl', function ($scope, $http, $route, AdminAlbum, Album) {
+module.controller('AdminAlbumCtrl', function ($scope, $http, $route, $location, AdminAlbum, Album) {
     $scope.albums = {};
     $http.get(apiUrl + '/admin/album').success(function (data) {
         $scope.albums = data;
     });
     $scope.deleteAlbum = function (album) {
-        var newAlbum = new Album(album);
-        newAlbum.$delete({id: album.id}, function () {
+        new Album(album).$delete({id: album.id}, function () {
             $route.reload();
         });
     }
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
index da78224..00f52d7 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/admin/albums.html
@@ -1,19 +1,19 @@
 <h1>All Albums</h1>
 <table class="table" data-ng-repeat="(key, value) in albums">
     <thead>
-        <tr>
-            <th>{{key}}</th>
-        </tr>
+    <tr>
+        <th>{{key}}</th>
+    </tr>
     </thead>
     <tbody>
-        <tr>
-            <td>
-                <ul>
-                    <li data-ng-repeat="p in value">
-                        <a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
-                    </li>
-                </ul>
-            </td>
-        </tr>
+    <tr>
+        <td>
+            <ul>
+                <li data-ng-repeat="p in value">
+                    <a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#/admin/album" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]
+                </li>
+            </ul>
+        </td>
+    </tr>
     </tbody>
 </table>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
index bd5853e..e144d1b 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/partials/home.html
@@ -1,4 +1,4 @@
-<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span> [<a href="" ng-click="Identity.logout()">Sign Out</a>]</h2>
+<h2><span>Welcome To Photoz, {{Identity.claims.name}}</span></h2>
 <div data-ng-show="Identity.isAdmin()"><b>Administration: </b> [<a href="#/admin/album" id="admin-albums">All Albums</a>]</div>
 <hr/>
 <br/>
@@ -9,14 +9,14 @@
 <span data-ng-show="albums.length == 0" id="resource-list-empty">You don't have any albums, yet.</span>
 <table class="table" data-ng-show="albums.length > 0">
     <thead>
-        <tr>
-            <th>Your Albums</th>
-        </tr>
+    <tr>
+        <th>Your Albums</th>
+    </tr>
     </thead>
     <tbody>
-        <tr data-ng-repeat="p in albums">
-            <td><a href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
-        </tr>
+    <tr data-ng-repeat="p in albums">
+        <td><a id="view-{{p.name}}" href="#/album/{{p.id}}">{{p.name}}</a> - [<a href="#" id="delete-{{p.name}}" ng-click="deleteAlbum(p)">X</a>]</td>
+    </tr>
     </tbody>
 </table>
 </div>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
index b3b2b81..baa8f66 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-realm.json
@@ -53,7 +53,7 @@
         }
       ],
       "realmRoles": [
-        "user", "admin", "uma_authorization"
+        "admin", "uma_authorization"
       ],
       "clientRoles": {
         "realm-management": [
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
index 388c9e4..c28bca3 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java
@@ -109,13 +109,7 @@ public class AlbumService {
 
     private void createProtectedResource(Album album) {
         try {
-            HashSet<ScopeRepresentation> scopes = new HashSet<>();
-
-            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_VIEW));
-            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_CREATE));
-            scopes.add(new ScopeRepresentation(SCOPE_ALBUM_DELETE));
-
-            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), scopes, "/album/" + album.getId(), "http://photoz.com/album");
+            ResourceRepresentation albumResource = new ResourceRepresentation(album.getName(), new HashSet(), "/album/" + album.getId(), "http://photoz.com/album");
 
             albumResource.setOwner(album.getUserId());
 
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
index cc8c8f8..6c786e7 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
@@ -21,10 +21,10 @@
           "name": "urn:photoz.com:scopes:album:view"
         },
         {
-          "name": "urn:photoz.com:scopes:album:create"
+          "name": "urn:photoz.com:scopes:album:delete"
         },
         {
-          "name": "urn:photoz.com:scopes:album:delete"
+          "name": "urn:photoz.com:scopes:album:create"
         }
       ]
     },
@@ -44,12 +44,15 @@
       "name": "Only Owner Policy",
       "description": "Defines that only the resource owner is allowed to do something",
       "type": "drools",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "mavenArtifactVersion": "2.1.0-SNAPSHOT",
         "mavenArtifactId": "photoz-authz-policy",
         "sessionName": "MainOwnerSession",
-        "mavenArtifactGroupId": "org.keycloak.testsuite",
+        "mavenArtifactGroupId": "org.keycloak",
         "moduleName": "PhotozAuthzOwnerPolicy",
+        "applyPolicies": "[]",
         "scannerPeriod": "1",
         "scannerPeriodUnit": "Hours"
       }
@@ -58,16 +61,22 @@
       "name": "Any Admin Policy",
       "description": "Defines that adminsitrators can do something",
       "type": "role",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
-        "roles": "[\"admin\"]"
+        "applyPolicies": "[]",
+        "roles": "[{\"id\":\"admin\",\"required\":true}]"
       }
     },
     {
       "name": "Any User Policy",
       "description": "Defines that any user can do something",
       "type": "role",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
-        "roles": "[\"user\"]"
+        "applyPolicies": "[]",
+        "roles": "[{\"id\":\"user\"}]"
       }
     },
     {
@@ -77,6 +86,7 @@
       "logic": "POSITIVE",
       "decisionStrategy": "UNANIMOUS",
       "config": {
+        "applyPolicies": "[]",
         "code": "var contextAttributes = $evaluation.getContext().getAttributes();\n\nif (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {\n    $evaluation.grant();\n}"
       }
     },
@@ -84,6 +94,8 @@
       "name": "Administration Policy",
       "description": "Defines that only administrators from a specific network address can do something.",
       "type": "aggregate",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "applyPolicies": "[\"Any Admin Policy\",\"Only From a Specific Client Address\"]"
       }
@@ -92,35 +104,28 @@
       "name": "Only Owner and Administrators Policy",
       "description": "Defines that only the resource owner and administrators can do something",
       "type": "aggregate",
+      "logic": "POSITIVE",
       "decisionStrategy": "AFFIRMATIVE",
       "config": {
-        "applyPolicies": "[\"Administration Policy\",\"Only Owner Policy\"]"
+        "applyPolicies": "[\"Only Owner Policy\",\"Administration Policy\"]"
       }
     },
     {
       "name": "Only From @keycloak.org or Admin",
       "description": "Defines that only users from @keycloak.org",
       "type": "js",
+      "logic": "POSITIVE",
+      "decisionStrategy": "UNANIMOUS",
       "config": {
         "applyPolicies": "[]",
         "code": "var context = $evaluation.getContext();\nvar identity = context.getIdentity();\nvar attributes = identity.getAttributes();\nvar email = attributes.getValue('email').asString(0);\n\nif (identity.hasRole('admin') || email.endsWith('@keycloak.org')) {\n    $evaluation.grant();\n}"
       }
     },
     {
-      "name": "Only in the Period",
-      "description": "Access granted only during the morning",
-      "type": "time",
-      "config": {
-        "noa": "2016-01-03 23:59:59",
-        "expirationUnit": "Minutes",
-        "nbf": "2016-01-01 00:00:00",
-        "expirationTime": "1"
-      }
-    },
-    {
       "name": "Album Resource Permission",
       "description": "General policies that apply to all album resources.",
       "type": "resource",
+      "logic": "POSITIVE",
       "decisionStrategy": "AFFIRMATIVE",
       "config": {
         "defaultResourceType": "http://photoz.com/album",
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 09bf6a9..0c3b108 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
@@ -76,17 +76,28 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl {
         pause(500);
     }
 
-    public void login(String username, String password) {
+    public void login(String username, String password) throws InterruptedException {
         navigateTo();
-
+        Thread.sleep(2000);
         if (this.driver.getCurrentUrl().startsWith(getInjectedUrl().toString())) {
+            Thread.sleep(2000);
             logOut();
+            navigateTo();
         }
 
+        Thread.sleep(2000);
+
         this.loginPage.form().login(username, password);
     }
 
     public boolean wasDenied() {
         return this.driver.findElement(By.id("output")).getText().contains("You can not access");
     }
+
+    public void viewAlbum(String name) {
+        By id = By.id("view-" + name);
+        WaitUtils.waitUntilElement(id);
+        this.driver.findElements(id).forEach(WebElement::click);
+        pause(500);
+    }
 }
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 32f23be..59a7a31 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
@@ -16,12 +16,10 @@
  */
 package org.keycloak.testsuite.adapter.example.authorization;
 
-import org.apache.commons.io.IOUtils;
 import org.jboss.arquillian.container.test.api.Deployer;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.arquillian.test.api.ArquillianResource;
-import org.jboss.shrinkwrap.api.asset.StringAsset;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.AuthorizationResource;
@@ -39,7 +37,6 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.List;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import static org.junit.Assert.assertFalse;
@@ -144,7 +141,6 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
                 }
             }
 
-            this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
             this.clientPage.deleteAlbum("Alice-Family-Album");
 
@@ -186,10 +182,124 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
                 }
             }
 
+            this.clientPage.navigateToAdminAlbum();
+
+            assertTrue(this.clientPage.wasDenied());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testAdminWithoutPermissionsToTypedResource() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+            this.clientPage.login("alice", "alice");
+            this.clientPage.createAlbum("Alice Family Album");
+
             this.clientPage.login("admin", "admin");
             this.clientPage.navigateToAdminAlbum();
 
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.viewAlbum("Alice Family Album");
+
+            assertFalse(this.clientPage.wasDenied());
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Album Resource Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Any User Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum("Alice Family Album");
+
             assertTrue(this.clientPage.wasDenied());
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Album Resource Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Any User Policy\", \"Administration Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum("Alice Family Album");
+
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.deleteAlbum("Alice Family Album");
+
+            List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
+
+            assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
+        } finally {
+            this.deployer.undeploy(RESOURCE_SERVER_ID);
+        }
+    }
+
+    @Test
+    public void testAdminWithoutPermissionsToDeleteScopePermission() throws Exception {
+        try {
+            this.deployer.deploy(RESOURCE_SERVER_ID);
+            this.clientPage.login("alice", "alice");
+            this.clientPage.createAlbum("Alice Family Album");
+
+            this.clientPage.login("admin", "admin");
+            this.clientPage.navigateToAdminAlbum();
+
+            assertFalse(this.clientPage.wasDenied());
+
+            this.clientPage.deleteAlbum("Alice Family Album");
+
+            assertFalse(this.clientPage.wasDenied());
+
+            List<ResourceRepresentation> resources = getAuthorizationResource().resources().resources();
+
+            assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Delete Album Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Only Owner Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            this.clientPage.login("alice", "alice");
+            this.clientPage.createAlbum("Alice Family Album");
+
+            this.clientPage.login("admin", "admin");
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.viewAlbum("Alice Family Album");
+
+            assertFalse(this.clientPage.wasDenied());
+            resources = getAuthorizationResource().resources().resources();
+
+            assertFalse(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
+
+            this.clientPage.navigateToAdminAlbum();
+
+            this.clientPage.deleteAlbum("Alice Family Album");
+            assertTrue(this.clientPage.wasDenied());
+
+            for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+                if ("Delete Album Permission".equals(policy.getName())) {
+                    policy.getConfig().put("applyPolicies", "[\"Only Owner and Administrators Policy\"]");
+                    getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+                }
+            }
+
+            this.clientPage.navigateToAdminAlbum();
+            this.clientPage.deleteAlbum("Alice Family Album");
+
+            assertFalse(this.clientPage.wasDenied());
+
+            resources = getAuthorizationResource().resources().resources();
+
+            assertTrue(resources.stream().filter(resource -> resource.getOwner().getName().equals("alice")).collect(Collectors.toList()).isEmpty());
         } finally {
             this.deployer.undeploy(RESOURCE_SERVER_ID);
         }
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 fbfe64b..50d5919 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
@@ -953,6 +953,7 @@ authz-no-resources=No resources
 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 Settings
 authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server.
@@ -1016,7 +1017,9 @@ authz-select-a-policy=Select a policy
 # Authz Role Policy Detail
 authz-add-role-policy=Add Role Policy
 authz-no-roles-assigned=No roles assigned.
-authz-policy-role-roles.tooltip=Specifies which role(s) are allowed by this policy.
+authz-policy-role-realm-roles.tooltip=Specifies which *realm* role(s) are allowed by this policy.
+authz-policy-role-clients.tooltip=Selects a client in order to filter the client roles that can be applied to this policy.
+authz-policy-role-client-roles.tooltip=Specifies which *client* role(s) are allowed by this policy.
 
 # Authz User Policy Detail
 authz-add-user-policy=Add User Policy
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 680cb5b..bbe0fd0 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
@@ -150,6 +150,14 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
                 client : client.id,
                 rsrid : $route.current.params.rsrid,
             }, function(data) {
+                if (!data.scopes) {
+                    data.scopes = [];
+                }
+
+                if (!data.policies) {
+                    data.policies = [];
+                }
+
                 $scope.resource = angular.copy(data);
                 $scope.changed = false;
 
@@ -157,9 +165,7 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r
                     $scope.resource.scopes[i] = $scope.resource.scopes[i].name;
                 }
 
-                data = angular.copy($scope.resource);
-
-                $scope.originalResource = data;
+                $scope.originalResource = angular.copy($scope.resource);
 
                 $scope.$watch('resource', function() {
                     if (!angular.equals($scope.resource, data)) {
@@ -237,6 +243,10 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo
         ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) {
             $scope.scopes = data;
         });
+
+        $scope.createPolicy = function(scope) {
+            $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id});
+        }
     });
 });
 
@@ -534,7 +544,7 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro
     }, realm, client, $scope);
 });
 
-module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
+module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, $location, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope) {
     PolicyController.onInit({
         getPolicyType : function() {
             return "scope";
@@ -624,6 +634,12 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route
             newPolicy.decisionStrategy = 'UNANIMOUS';
             newPolicy.config = {};
             newPolicy.config.resources = '';
+
+            var scopeId = $location.search()['scpid'];
+
+            if (scopeId) {
+                newPolicy.config.scopes = [scopeId];
+            }
         },
 
         onCreate : function() {
@@ -712,7 +728,7 @@ module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route,
     }, realm, client, $scope);
 });
 
-module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, PolicyController, Role, RoleById) {
+module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, Client, ClientRole, PolicyController, Role, RoleById) {
     PolicyController.onInit({
         getPolicyType : function() {
             return "role";
@@ -723,6 +739,10 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
                 $scope.roles = data;
             });
 
+            Client.query({realm: $route.current.params.realm}, function (data) {
+                $scope.clients = data;
+            });
+
             $scope.selectedRoles = [];
 
             $scope.selectRole = function(role) {
@@ -732,10 +752,55 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
 
                 $scope.selectedRole = {};
                 $scope.selectedRoles.push(role);
+
+                var clientRoles = [];
+
+                if ($scope.clientRoles) {
+                    for (i = 0; i < $scope.clientRoles.length; i++) {
+                        if ($scope.clientRoles[i].id != role.id) {
+                            clientRoles.push($scope.clientRoles[i]);
+                        }
+                    }
+                    $scope.clientRoles = clientRoles;
+                }
             }
 
-            $scope.removeFromList = function(list, index) {
-                list.splice(index, 1);
+            $scope.removeFromList = function(role) {
+                if ($scope.clientRoles && $scope.selectedClient && $scope.selectedClient.id == role.containerId) {
+                    $scope.clientRoles.push(role);
+                }
+                var index = $scope.selectedRoles.indexOf(role);
+                if (index != -1) {
+                    $scope.selectedRoles.splice(index, 1);
+                }
+            }
+
+            $scope.selectClient = function() {
+                if (!$scope.selectedClient) {
+                    $scope.clientRoles = [];
+                    return;
+                }
+                ClientRole.query({realm: $route.current.params.realm, client: $scope.selectedClient.id}, function(data) {
+                    var roles = [];
+
+                    for (j = 0; j < data.length; j++) {
+                        var defined = false;
+
+                        for (i = 0; i < $scope.selectedRoles.length; i++) {
+                            if ($scope.selectedRoles[i].id == data[j].id) {
+                                defined = true;
+                                break;
+                            }
+                        }
+
+                        if (!defined) {
+                            data[j].container = {};
+                            data[j].container.name = $scope.selectedClient.clientId;
+                            roles.push(data[j]);
+                        }
+                    }
+                    $scope.clientRoles = roles;
+                });
             }
         },
 
@@ -746,7 +811,18 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
                 var roles = eval(policy.config.roles);
 
                 for (i = 0; i < roles.length; i++) {
-                    RoleById.get({realm: $route.current.params.realm, role: roles[i]}, function(data) {
+                    RoleById.get({realm: $route.current.params.realm, role: roles[i].id}, function(data) {
+                        for (i = 0; i < roles.length; i++) {
+                            if (roles[i].id == data.id) {
+                                data.required = roles[i].required ? true : false;
+                            }
+                        }
+                        for (i = 0; i < $scope.clients.length; i++) {
+                            if ($scope.clients[i].id == data.containerId) {
+                                data.container = {};
+                                data.container.name = $scope.clients[i].clientId;
+                            }
+                        }
                         selectedRoles.push(data);
                         $scope.selectedRoles = angular.copy(selectedRoles);
                     });
@@ -764,7 +840,12 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
             var roles = [];
 
             for (i = 0; i < $scope.selectedRoles.length; i++) {
-                roles.push($scope.selectedRoles[i].id);
+                var role = {};
+                role.id = $scope.selectedRoles[i].id;
+                if ($scope.selectedRoles[i].required) {
+                    role.required = $scope.selectedRoles[i].required;
+                }
+                roles.push(role);
             }
 
             $scope.policy.config.roles = JSON.stringify(roles);
@@ -774,12 +855,35 @@ module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route,
             var roles = [];
 
             for (i = 0; i < $scope.selectedRoles.length; i++) {
-                roles.push($scope.selectedRoles[i].id);
+                var role = {};
+                role.id = $scope.selectedRoles[i].id;
+                if ($scope.selectedRoles[i].required) {
+                    role.required = $scope.selectedRoles[i].required;
+                }
+                roles.push(role);
             }
 
             $scope.policy.config.roles = JSON.stringify(roles);
         }
     }, realm, client, $scope);
+    
+    $scope.hasRealmRole = function () {
+        for (i = 0; i < $scope.selectedRoles.length; i++) {
+            if (!$scope.selectedRoles[i].clientRole) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    $scope.hasClientRole = function () {
+        for (i = 0; i < $scope.selectedRoles.length; i++) {
+            if ($scope.selectedRoles[i].clientRole) {
+                return true;
+            }
+        }
+        return false;
+    }
 });
 
 module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
index 9b0c199..8904bb3 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html
@@ -49,31 +49,85 @@
                 <kc-tooltip>{{:: 'authz-policy-description.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group clearfix">
-                <label class="col-md-2 control-label" for="roles">{{:: 'roles' | translate}} <span class="required">*</span></label>
+                <label class="col-md-2 control-label" for="roles">{{:: 'realm-roles' | translate}} <span class="required">*</span></label>
 
-                <div class="col-md-6">
+                <div class="col-md-4">
                     <select ui-select2="{ minimumInputLength: 1}" id="roles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
                             ng-options="role as role.name for role in roles" data-ng-required="selectedUsers.length == 0 && selectedRoles.length == 0">
                     </select>
                 </div>
 
-                <kc-tooltip>{{:: 'authz-policy-role-roles.tooltip' | translate}}</kc-tooltip>
+                <kc-tooltip>{{:: 'authz-policy-role-realm-roles.tooltip' | translate}}</kc-tooltip>
             </div>
             <div class="form-group clearfix" style="margin-top: -15px;">
                 <label class="col-md-2 control-label"></label>
-                <div class="col-sm-3">
+                <div class="col-sm-4" data-ng-show="hasRealmRole()">
                     <table class="table table-striped table-bordered">
                         <thead>
-                            <tr data-ng-hide="!selectedRoles.length">
-                                <th>{{:: 'name' | translate}}</th>
+                        <tr>
+                            <th class="col-sm-5">{{:: 'name' | translate}}</th>
+                            <th>{{:: 'authz-required' | translate}}</th>
+                            <th>{{:: 'actions' | translate}}</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        <tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="!role.clientRole">
+                            <td>{{role.name}}</td>
+                            <td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
+                            <td class="kc-action-cell">
+                                <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
+                            </td>
+                        </tr>
+                        <tr data-ng-show="!selectedRoles.length">
+                            <td class="text-muted" colspan="3">{{:: 'authz-no-roles-assigned' | translate}}</td>
+                        </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="clients">{{:: 'clients' | translate}}</label>
+
+                <div class="col-md-4">
+                    <select class="form-control" id="clients"
+                            ng-model="selectedClient"
+                            ng-change="selectClient()"
+                            data-ng-options="current as current.clientId for current in clients">
+                        <option value="">{{:: 'selectOne' | translate}}...</option>
+                    </select>
+                </div>
+                <kc-tooltip>{{:: 'authz-policy-role-clients.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix">
+                <label class="col-md-2 control-label" for="clientRoles">{{:: 'client-roles' | translate}} <span class="required">*</span></label>
+
+                <div class="col-md-4">
+                    <select ui-select2="{ minimumInputLength: 1}" id="clientRoles" data-ng-model="selectedRole" data-ng-change="selectRole(selectedRole);" data-placeholder="{{:: 'select-a-role' | translate}}..."
+                            ng-options="role as role.name for role in clientRoles" data-ng-required="selectedRoles.length == 0" data-ng-disabled="!selectedClient">
+                    </select>
+                </div>
+
+                <kc-tooltip>{{:: 'authz-policy-role-client-roles.tooltip' | translate}}</kc-tooltip>
+            </div>
+            <div class="form-group clearfix" style="margin-top: -15px;">
+                <label class="col-md-2 control-label"></label>
+                <div class="col-sm-4" data-ng-show="hasClientRole()">
+                    <table class="table table-striped table-bordered">
+                        <thead>
+                            <tr>
+                                <th class="col-sm-5">{{:: 'name' | translate}}</th>
+                                <th class="col-sm-5">{{:: 'client' | translate}}</th>
+                                <th>{{:: 'authz-required' | translate}}</th>
                                 <th>{{:: 'actions' | translate}}</th>
                             </tr>
                         </thead>
                         <tbody>
-                            <tr ng-repeat="role in selectedRoles | orderBy:'name'">
+                            <tr ng-repeat="role in selectedRoles | orderBy:'name'" ng-if="role.clientRole">
                                 <td>{{role.name}}</td>
+                                <td>{{role.container.name}}</td>
+                                <td><input type="checkbox" ng-model="role.required" id="{{role.id}}"></td>
                                 <td class="kc-action-cell">
-                                    <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(selectedRoles, $index);">{{:: 'remove' | translate}}</button>
+                                    <button class="btn btn-default btn-block btn-sm" ng-click="removeFromList(role);">{{:: 'remove' | translate}}</button>
                                 </td>
                             </tr>
                             <tr data-ng-show="!selectedRoles.length">
@@ -98,7 +152,6 @@
             </div>
             <input type="hidden" data-ng-model="policy.type"/>
         </fieldset>
-
         <div class="form-group" data-ng-show="access.manageAuthorization">
             <div class="col-md-10 col-md-offset-2">
                 <button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
index 8a748f4..524d55d 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html
@@ -43,14 +43,14 @@
                         <ul>
                             <li data-ng-repeat="policyResult in result.policies">
                                 <strong><a
-                                        href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
+                                        href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policyResult.policy.type}}/{{policyResult.policy.id}}">{{policyResult.policy.name}}</a></strong>
                                 decision was <span style="color: green" data-ng-show="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
                                 <span style="color: red" data-ng-hide="policyResult.status == 'PERMIT'"><strong>{{policyResult.status}}</strong></span>
                                 by <strong>{{policyResult.policy.decisionStrategy}}</strong> decision.</a>
                                 <ul>
                                     <li data-ng-repeat="subPolicy in policyResult.associatedPolicies">
                                         <strong><a
-                                                href="#/realms/{{realm.realm}}/authz/resource-server/{{server.id}}/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
+                                                href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/policy/{{subPolicy.policy.type}}/{{subPolicy.policy.id}}">{{subPolicy.policy.name}}</a></strong>
                                         voted to <span style="color: green"
                                                        data-ng-show="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>
                                         <span style="color: red" data-ng-hide="subPolicy.status == 'PERMIT'"><strong>{{subPolicy.status}}</strong></span>.</a>
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 8d1b0e1..39cfc77 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
@@ -24,11 +24,33 @@
             </tr>
             <tr data-ng-hide="scopes.length == 0">
                 <th>{{:: 'name' | translate}}</th>
+                <th>{{:: 'authz-resources' | translate}}</th>
+                <th>{{:: 'authz-permissions' | translate}}</th>
+                <th>{{:: 'actions' | translate}}</th>
             </tr>
         </thead>
         <tbody>
             <tr ng-repeat="scope in scopes | filter:search | orderBy:'name'">
                 <td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/scope/{{scope.id}}">{{scope.name}}</a></td>
+                <td>
+                    <span data-ng-show="!scope.resources.length">{{:: 'authz-no-resources-assigned' | translate}}</span>
+                    <span data-ng-show="scope.resources.length > 0">
+                        <span ng-repeat="resource in scope.resources">
+                            <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/resource/{{resource._id}}">{{resource.name}}</a>{{$last ? '' : ', '}}
+                        </span>
+                    </span>
+                </td>
+                <td>
+                    <span data-ng-show="!scope.policies.length">{{:: 'authz-no-permission-assigned' | translate}}</span>
+                    <span data-ng-show="scope.policies.length > 0">
+                        <span ng-repeat="policy in scope.policies">
+                            <a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server/permission/{{policy.type}}/{{policy.id}}">{{policy.name}}</a>{{$last ? '' : ', '}}
+                        </span>
+                    </span>
+                </td>
+                <td class="kc-action-cell" style="vertical-align: middle">
+                    <button class="btn btn-default btn-block btn-sm" ng-click="createPolicy(scope);">{{:: 'authz-create-permission' | translate}}</button>
+                </td>
             </tr>
             <tr data-ng-show="(scopes | filter:search).length == 0">
                 <td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>