keycloak-aplcache

[KEYCLOAK-4755] - Client UI Tests

4/26/2017 12:11:53 PM

Changes

Details

diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java
index 5c778d8..ffd92bf 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProvider.java
@@ -1,6 +1,6 @@
 package org.keycloak.authorization.policy.provider.client;
 
-import static org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory.getClients;
+import java.util.function.Function;
 
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.Policy;
@@ -9,24 +9,29 @@ 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.representations.idm.authorization.ClientPolicyRepresentation;
 
 public class ClientPolicyProvider implements PolicyProvider {
 
+    private final Function<Policy, ClientPolicyRepresentation> representationFunction;
+
+    public ClientPolicyProvider(Function<Policy, ClientPolicyRepresentation> representationFunction) {
+        this.representationFunction = representationFunction;
+    }
+
     @Override
     public void evaluate(Evaluation evaluation) {
-        Policy policy = evaluation.getPolicy();
-        EvaluationContext context = evaluation.getContext();
-        String[] clients = getClients(policy);
+        ClientPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy());
         AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
         RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
+        EvaluationContext context = evaluation.getContext();
+
+        for (String client : representation.getClients()) {
+            ClientModel clientModel = realm.getClientById(client);
 
-        if (clients.length > 0) {
-            for (String client : clients) {
-                ClientModel clientModel = realm.getClientById(client);
-                if (context.getAttributes().containsValue("kc.client.id", clientModel.getClientId())) {
-                    evaluation.grant();
-                    return;
-                }
+            if (context.getAttributes().containsValue("kc.client.id", clientModel.getClientId())) {
+                evaluation.grant();
+                return;
             }
         }
     }
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
index a0e7874..d061357 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/client/ClientPolicyProviderFactory.java
@@ -2,14 +2,17 @@ package org.keycloak.authorization.policy.provider.client;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.keycloak.Config;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
-import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
 import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
 import org.keycloak.authorization.store.PolicyStore;
 import org.keycloak.authorization.store.ResourceServerStore;
@@ -17,12 +20,15 @@ import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmModel.ClientRemovedEvent;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.util.JsonSerialization;
 
-public class ClientPolicyProviderFactory implements PolicyProviderFactory {
+public class ClientPolicyProviderFactory implements PolicyProviderFactory<ClientPolicyRepresentation> {
 
-    private ClientPolicyProvider provider = new ClientPolicyProvider();
+    private ClientPolicyProvider provider = new ClientPolicyProvider(policy -> toRepresentation(policy, new ClientPolicyRepresentation()));
 
     @Override
     public String getName() {
@@ -40,8 +46,29 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory {
     }
 
     @Override
-    public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
-        return null;
+    public ClientPolicyRepresentation toRepresentation(Policy policy, ClientPolicyRepresentation representation) {
+        representation.setClients(new HashSet<>(Arrays.asList(getClients(policy))));
+        return representation;
+    }
+
+    @Override
+    public Class<ClientPolicyRepresentation> getRepresentationType() {
+        return ClientPolicyRepresentation.class;
+    }
+
+    @Override
+    public void onCreate(Policy policy, ClientPolicyRepresentation representation, AuthorizationProvider authorization) {
+        updateClients(policy, representation.getClients(), authorization);
+    }
+
+    @Override
+    public void onUpdate(Policy policy, ClientPolicyRepresentation representation, AuthorizationProvider authorization) {
+        updateClients(policy, representation.getClients(), authorization);
+    }
+
+    @Override
+    public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) {
+        updateClients(policy, new HashSet<>(Arrays.asList(getClients(policy))), authorization);
     }
 
     @Override
@@ -101,7 +128,41 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory {
         return "client";
     }
 
-    static String[] getClients(Policy policy) {
+    private void updateClients(Policy policy, Set<String> clients, AuthorizationProvider authorization) {
+        RealmModel realm = authorization.getKeycloakSession().getContext().getRealm();
+
+        if (clients == null || clients.isEmpty()) {
+            throw new RuntimeException("No client provided.");
+        }
+
+        Set<String> updatedClients = new HashSet<>();
+
+        for (String id : clients) {
+            ClientModel client = realm.getClientByClientId(id);
+
+            if (client == null) {
+                client = realm.getClientById(id);
+            }
+
+            if (client == null) {
+                throw new RuntimeException("Error while updating policy [" + policy.getName()  + "]. Client [" + id + "] could not be found.");
+            }
+
+            updatedClients.add(client.getId());
+        }
+
+        try {
+            Map<String, String> config = policy.getConfig();
+
+            config.put("clients", JsonSerialization.writeValueAsString(updatedClients));
+
+            policy.setConfig(config);
+        } catch (IOException cause) {
+            throw new RuntimeException("Failed to serialize clients", cause);
+        }
+    }
+
+    private String[] getClients(Policy policy) {
         String clients = policy.getConfig().get("clients");
 
         if (clients != null) {
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 4aafedd..e88eed6 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/role/RolePolicyProvider.java
@@ -17,9 +17,9 @@
  */
 package org.keycloak.authorization.policy.provider.role;
 
-import static org.keycloak.authorization.policy.provider.role.RolePolicyProviderFactory.getRoles;
-
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
 
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.identity.Identity;
@@ -29,43 +29,43 @@ import org.keycloak.authorization.policy.provider.PolicyProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class RolePolicyProvider implements PolicyProvider {
 
+    private final Function<Policy, RolePolicyRepresentation> representationFunction;
+
+    public RolePolicyProvider(Function<Policy, RolePolicyRepresentation> representationFunction) {
+        this.representationFunction = representationFunction;
+    }
+
     @Override
     public void evaluate(Evaluation evaluation) {
         Policy policy = evaluation.getPolicy();
-        Map<String, Object>[] roleIds = getRoles(policy);
+        Set<RolePolicyRepresentation.RoleDefinition> roleIds = representationFunction.apply(policy).getRoles();
         AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
         RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
+        Identity identity = evaluation.getContext().getIdentity();
 
-        if (roleIds.length > 0) {
-            Identity identity = evaluation.getContext().getIdentity();
+        for (RolePolicyRepresentation.RoleDefinition roleDefinition : roleIds) {
+            RoleModel role = realm.getRoleById(roleDefinition.getId());
 
-            for (Map<String, Object> current : roleIds) {
-                RoleModel role = realm.getRoleById((String) current.get("id"));
+            if (role != null) {
+                boolean hasRole = hasRole(identity, role, realm);
 
-                if (role != null) {
-                    boolean hasRole = hasRole(identity, role, realm);
-
-                    if (!hasRole && Boolean.valueOf(isRequired(current))) {
-                        evaluation.deny();
-                        return;
-                    } else if (hasRole) {
-                        evaluation.grant();
-                    }
+                if (!hasRole && roleDefinition.isRequired()) {
+                    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, RealmModel realm) {
         String roleName = role.getName();
         if (role.isClientRole()) {
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 d5d3917..03ea156 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
@@ -23,7 +23,6 @@ import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
-import org.keycloak.authorization.policy.provider.PolicyProviderAdminService;
 import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
 import org.keycloak.authorization.store.PolicyStore;
 import org.keycloak.authorization.store.ResourceServerStore;
@@ -53,7 +52,7 @@ import java.util.Set;
  */
 public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePolicyRepresentation> {
 
-    private RolePolicyProvider provider = new RolePolicyProvider();
+    private RolePolicyProvider provider = new RolePolicyProvider(policy -> toRepresentation(policy, new RolePolicyRepresentation()));
 
     @Override
     public String getName() {
@@ -71,19 +70,14 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
     }
 
     @Override
-    public PolicyProviderAdminService getAdminResource(ResourceServer resourceServer, AuthorizationProvider authorization) {
-        return null;
-    }
-
-    @Override
     public PolicyProvider create(KeycloakSession session) {
-        return new RolePolicyProvider();
+        return provider;
     }
 
     @Override
     public RolePolicyRepresentation toRepresentation(Policy policy, RolePolicyRepresentation representation) {
         try {
-            representation.setRoles(JsonSerialization.readValue(policy.getConfig().get("roles"), Set.class));
+            representation.setRoles(new HashSet<>(Arrays.asList(JsonSerialization.readValue(policy.getConfig().get("roles"), RolePolicyRepresentation.RoleDefinition[].class))));
         } catch (IOException cause) {
             throw new RuntimeException("Failed to deserialize roles", cause);
         }
@@ -119,65 +113,63 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
     }
 
     private void updateRoles(Policy policy, AuthorizationProvider authorization, Set<RolePolicyRepresentation.RoleDefinition> roles) {
-        try {
-            RealmModel realm = authorization.getRealm();
-            Set<RolePolicyRepresentation.RoleDefinition> updatedRoles = new HashSet<>();
-
-            if (roles != null) {
-                for (RolePolicyRepresentation.RoleDefinition definition : roles) {
-                    String roleName = definition.getId();
-                    String clientId = null;
-                    int clientIdSeparator = roleName.indexOf("/");
-
-                    if (clientIdSeparator != -1) {
-                        clientId = roleName.substring(0, clientIdSeparator);
-                        roleName = roleName.substring(clientIdSeparator + 1);
-                    }
-
-                    RoleModel role;
+        RealmModel realm = authorization.getRealm();
+        Set<RolePolicyRepresentation.RoleDefinition> updatedRoles = new HashSet<>();
 
-                    if (clientId == null) {
-                        role = realm.getRole(roleName);
-
-                        if (role == null) {
-                            role = realm.getRoleById(roleName);
-                        }
-                    } else {
-                        ClientModel client = realm.getClientByClientId(clientId);
+        if (roles != null) {
+            for (RolePolicyRepresentation.RoleDefinition definition : roles) {
+                String roleName = definition.getId();
+                String clientId = null;
+                int clientIdSeparator = roleName.indexOf("/");
+
+                if (clientIdSeparator != -1) {
+                    clientId = roleName.substring(0, clientIdSeparator);
+                    roleName = roleName.substring(clientIdSeparator + 1);
+                }
 
-                        if (client == null) {
-                            throw new RuntimeException("Client with id [" + clientId + "] not found.");
-                        }
+                RoleModel role;
 
-                        role = client.getRole(roleName);
-                    }
+                if (clientId == null) {
+                    role = realm.getRole(roleName);
 
-                    // fallback to find any client role with the given name
                     if (role == null) {
-                        String finalRoleName = roleName;
-                        role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null)
-                                .findFirst().orElse(null);
+                        role = realm.getRoleById(roleName);
                     }
+                } else {
+                    ClientModel client = realm.getClientByClientId(clientId);
 
-                    if (role == null) {
-                        throw new RuntimeException("Error while importing configuration. Role [" + roleName + "] could not be found.");
+                    if (client == null) {
+                        throw new RuntimeException("Client with id [" + clientId + "] not found.");
                     }
 
-                    definition.setId(role.getId());
+                    role = client.getRole(roleName);
+                }
 
-                    updatedRoles.add(definition);
+                // fallback to find any client role with the given name
+                if (role == null) {
+                    String finalRoleName = roleName;
+                    role = realm.getClients().stream().map(clientModel -> clientModel.getRole(finalRoleName)).filter(roleModel -> roleModel != null)
+                            .findFirst().orElse(null);
                 }
-                try {
-                } catch (Exception e) {
-                    throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e);
+
+                if (role == null) {
+                    throw new RuntimeException("Error while updating policy [" + policy.getName()  + "]. Role [" + roleName + "] could not be found.");
                 }
+
+                definition.setId(role.getId());
+
+                updatedRoles.add(definition);
             }
+        }
 
+        try {
             Map<String, String> config = policy.getConfig();
+
             config.put("roles", JsonSerialization.writeValueAsString(updatedRoles));
+
             policy.setConfig(config);
         } catch (IOException cause) {
-            throw new RuntimeException("Failed to deserialize roles", cause);
+            throw new RuntimeException("Failed to serialize roles", cause);
         }
     }
 
@@ -253,7 +245,7 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory<RolePoli
         return "role";
     }
 
-    static Map<String, Object>[] getRoles(Policy policy) {
+    private Map<String, Object>[] getRoles(Policy policy) {
         String roles = policy.getConfig().get("roles");
 
         if (roles != null) {
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java
index 2f77106..f891257 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProvider.java
@@ -17,30 +17,34 @@
  */
 package org.keycloak.authorization.policy.provider.user;
 
+import java.util.function.Function;
+
 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 static org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory.getUsers;
+import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class UserPolicyProvider implements PolicyProvider {
 
+    private final Function<Policy, UserPolicyRepresentation> representationFunction;
+
+    public UserPolicyProvider(Function<Policy, UserPolicyRepresentation> representationFunction) {
+        this.representationFunction = representationFunction;
+    }
+
     @Override
     public void evaluate(Evaluation evaluation) {
-        Policy policy = evaluation.getPolicy();
         EvaluationContext context = evaluation.getContext();
-        String[] userIds = getUsers(policy);
-
-        if (userIds.length > 0) {
-            for (String userId : userIds) {
-                if (context.getIdentity().getId().equals(userId)) {
-                    evaluation.grant();
-                    break;
-                }
+        UserPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy());
+
+        for (String userId : representation.getUsers()) {
+            if (context.getIdentity().getId().equals(userId)) {
+                evaluation.grant();
+                break;
             }
         }
     }
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
index 0fd54df..e4bbaf9 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/user/UserPolicyProviderFactory.java
@@ -24,6 +24,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 import org.keycloak.Config;
 import org.keycloak.authorization.AuthorizationProvider;
@@ -49,7 +50,7 @@ import org.keycloak.util.JsonSerialization;
  */
 public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPolicyRepresentation> {
 
-    private UserPolicyProvider provider = new UserPolicyProvider();
+    private UserPolicyProvider provider = new UserPolicyProvider((Function<Policy, UserPolicyRepresentation>) policy -> toRepresentation(policy, new UserPolicyRepresentation()));
 
     @Override
     public String getName() {
@@ -110,42 +111,40 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory<UserPoli
     }
 
     private void updateUsers(Policy policy, AuthorizationProvider authorization, Set<String> users) {
-        try {
-            KeycloakSession session = authorization.getKeycloakSession();
-            RealmModel realm = authorization.getRealm();
-            UserProvider userProvider = session.users();
-            Set<String> updatedUsers = new HashSet<>();
-
-            if (users != null) {
-                try {
-                    for (String userId : users) {
-                        UserModel user = null;
+        KeycloakSession session = authorization.getKeycloakSession();
+        RealmModel realm = authorization.getRealm();
+        UserProvider userProvider = session.users();
+        Set<String> updatedUsers = new HashSet<>();
 
-                        try {
-                            user = userProvider.getUserByUsername(userId, realm);
-                        } catch (Exception ignore) {
-                        }
+        if (users != null) {
+            for (String userId : users) {
+                UserModel user = null;
 
-                        if (user == null) {
-                            user = userProvider.getUserById(userId, realm);
-                        }
+                try {
+                    user = userProvider.getUserByUsername(userId, realm);
+                } catch (Exception ignore) {
+                }
 
-                        if (user == null) {
-                            throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found.");
-                        }
+                if (user == null) {
+                    user = userProvider.getUserById(userId, realm);
+                }
 
-                        updatedUsers.add(user.getId());
-                    }
-                } catch (Exception e) {
-                    throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e);
+                if (user == null) {
+                    throw new RuntimeException("Error while updating policy [" + policy.getName()  + "]. User [" + userId + "] could not be found.");
                 }
+
+                updatedUsers.add(user.getId());
             }
+        }
 
+        try {
             Map<String, String> config = policy.getConfig();
+
             config.put("users", JsonSerialization.writeValueAsString(updatedUsers));
+
             policy.setConfig(config);
         } catch (IOException cause) {
-            throw new RuntimeException("Failed to deserialize roles", cause);
+            throw new RuntimeException("Failed to serialize users", cause);
         }
     }
 
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/ClientPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/ClientPolicyRepresentation.java
new file mode 100644
index 0000000..d922a8f
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/ClientPolicyRepresentation.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.representations.idm.authorization;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClientPolicyRepresentation extends AbstractPolicyRepresentation {
+
+    private Set<String> clients;
+
+    @Override
+    public String getType() {
+        return "client";
+    }
+
+    public Set<String> getClients() {
+        return clients;
+    }
+
+    public void setClients(Set<String> clients) {
+        this.clients = clients;
+    }
+
+    public void addClient(String... id) {
+        if (this.clients == null) {
+            this.clients = new HashSet<>();
+        }
+        this.clients.addAll(Arrays.asList(id));
+    }
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesResource.java
new file mode 100644
index 0000000..3fd7778
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesResource.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.admin.client.resource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface ClientPoliciesResource {
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    Response create(ClientPolicyRepresentation representation);
+
+    @Path("{id}")
+    ClientPolicyResource findById(@PathParam("id") String id);
+
+    @Path("/search")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    ClientPolicyRepresentation findByName(@QueryParam("name") String name);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPolicyResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPolicyResource.java
new file mode 100644
index 0000000..a944935
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPolicyResource.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.admin.client.resource;
+
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface ClientPolicyResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    ClientPolicyRepresentation toRepresentation();
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    void update(ClientPolicyRepresentation representation);
+
+    @DELETE
+    void remove();
+
+    @Path("/associatedPolicies")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    List<PolicyRepresentation> associatedPolicies();
+
+    @Path("/dependentPolicies")
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @NoCache
+    List<PolicyRepresentation> dependentPolicies();
+
+    @Path("/resources")
+    @GET
+    @Produces("application/json")
+    @NoCache
+    List<ResourceRepresentation> resources();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java
index 4ce362d..a0af5d4 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java
@@ -70,10 +70,10 @@ public interface PoliciesResource {
     PolicyEvaluationResponse evaluate(PolicyEvaluationRequest evaluationRequest);
 
     @Path("role")
-    RolePoliciesResource roles();
+    RolePoliciesResource role();
 
     @Path("user")
-    UserPoliciesResource users();
+    UserPoliciesResource user();
 
     @Path("js")
     JSPoliciesResource js();
@@ -86,4 +86,7 @@ public interface PoliciesResource {
 
     @Path("rules")
     RulePoliciesResource rule();
+
+    @Path("client")
+    ClientPoliciesResource client();
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
index d61b077..5c3f1f1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzAdapterTest.java
@@ -103,7 +103,7 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda
         return getClientResource(RESOURCE_SERVER_ID).authorization();
     }
 
-    private ClientResource getClientResource(String clientId) {
+    protected ClientResource getClientResource(String clientId) {
         ClientsResource clients = this.realmsResouce().realm(REALM_NAME).clients();
         ClientRepresentation resourceServer = clients.findByClientId(clientId).get(0);
         return clients.get(resourceServer.getId());
@@ -199,7 +199,7 @@ public abstract class AbstractServletAuthzAdapterTest extends AbstractExampleAda
 
         assertFalse(policy.getUsers().isEmpty());
 
-        getAuthorizationResource().policies().users().create(policy);
+        getAuthorizationResource().policies().user().create(policy);
     }
 
     protected interface ExceptionRunnable {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
index 3aef537..b37e9e6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractServletAuthzFunctionalAdapterTest.java
@@ -26,15 +26,19 @@ import java.util.List;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Test;
-import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.ClientPoliciesResource;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.admin.client.resource.ResourcesResource;
+import org.keycloak.admin.client.resource.RolePoliciesResource;
+import org.keycloak.admin.client.resource.RoleScopeResource;
+import org.keycloak.admin.client.resource.RolesResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.admin.client.resource.UsersResource;
-import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
 import org.keycloak.testsuite.util.WaitUtils;
 
 /**
@@ -205,4 +209,99 @@ public abstract class AbstractServletAuthzFunctionalAdapterTest extends Abstract
             assertTrue(hasText("This is public resource that should be accessible without login."));
         });
     }
+
+    @Test
+    public void testRequiredRole() throws Exception {
+        performTests(() -> {
+            login("jdoe", "jdoe");
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+
+            RolesResource rolesResource = getClientResource(RESOURCE_SERVER_ID).roles();
+
+            rolesResource.create(new RoleRepresentation("required-role", "", false));
+
+            RolePolicyRepresentation policy = new RolePolicyRepresentation();
+
+            policy.setName("Required Role Policy");
+            policy.addRole("user_premium", false);
+            policy.addRole("required-role", false);
+
+            RolePoliciesResource rolePolicy = getAuthorizationResource().policies().role();
+
+            rolePolicy.create(policy);
+            policy = rolePolicy.findByName(policy.getName());
+
+            updatePermissionPolicies("Premium Resource Permission", policy.getName());
+
+            login("jdoe", "jdoe");
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+
+            policy.getRoles().clear();
+            policy.addRole("user_premium", false);
+            policy.addRole("required-role", true);
+
+            rolePolicy.findById(policy.getId()).update(policy);
+
+            login("jdoe", "jdoe");
+            navigateToUserPremiumPage();
+            assertTrue(wasDenied());
+
+            UsersResource users = realmsResouce().realm(REALM_NAME).users();
+            UserRepresentation user = users.search("jdoe").get(0);
+
+            RoleScopeResource roleScopeResource = users.get(user.getId()).roles().clientLevel(getClientResource(RESOURCE_SERVER_ID).toRepresentation().getId());
+            RoleRepresentation requiredRole = rolesResource.get("required-role").toRepresentation();
+            roleScopeResource.add(Arrays.asList(requiredRole));
+
+            login("jdoe", "jdoe");
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+
+            policy.getRoles().clear();
+            policy.addRole("user_premium", false);
+            policy.addRole("required-role", false);
+
+            rolePolicy.findById(policy.getId()).update(policy);
+
+            login("jdoe", "jdoe");
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+
+            roleScopeResource.remove(Arrays.asList(requiredRole));
+
+            login("jdoe", "jdoe");
+            navigateToUserPremiumPage();
+            assertFalse(wasDenied());
+        });
+    }
+
+    @Test
+    public void testOnlySpecificClient() throws Exception {
+        performTests(() -> {
+            login("jdoe", "jdoe");
+            assertFalse(wasDenied());
+
+            ClientPolicyRepresentation policy = new ClientPolicyRepresentation();
+
+            policy.setName("Only Client Policy");
+            policy.addClient("admin-cli");
+
+            ClientPoliciesResource policyResource = getAuthorizationResource().policies().client();
+            policyResource.create(policy);
+            policy = policyResource.findByName(policy.getName());
+
+            updatePermissionPolicies("Protected Resource Permission", policy.getName());
+
+            login("jdoe", "jdoe");
+            assertTrue(wasDenied());
+
+            policy.addClient("servlet-authz-app");
+            policyResource.findById(policy.getId()).update(policy);
+
+            login("jdoe", "jdoe");
+            assertFalse(wasDenied());
+        });
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPolicyManagementTest.java
index b1f0b24..e0a4c53 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPolicyManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractPolicyManagementTest.java
@@ -147,7 +147,7 @@ public abstract class AbstractPolicyManagementTest extends AbstractKeycloakTest 
         representation.setName(name);
         representation.addUser(userId);
 
-        client.authorization().policies().users().create(representation);
+        client.authorization().policies().user().create(representation);
     }
 
     protected ClientResource getClient() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClientPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClientPolicyManagementTest.java
new file mode 100644
index 0000000..87848d9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClientPolicyManagementTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.admin.client.authorization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+
+import org.junit.Test;
+import org.keycloak.admin.client.resource.AuthorizationResource;
+import org.keycloak.admin.client.resource.ClientPoliciesResource;
+import org.keycloak.admin.client.resource.ClientPolicyResource;
+import org.keycloak.admin.client.resource.PolicyResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.RealmBuilder;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClientPolicyManagementTest extends AbstractPolicyManagementTest {
+
+    @Override
+    protected RealmBuilder createTestRealm() {
+        return super.createTestRealm()
+                .client(ClientBuilder.create().clientId("Client A"))
+                .client(ClientBuilder.create().clientId("Client B"))
+                .client(ClientBuilder.create().clientId("Client C"));
+    }
+
+    @Test
+    public void testCreate() {
+        AuthorizationResource authorization = getClient().authorization();
+        ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
+
+        representation.setName("Realm Client Policy");
+        representation.setDescription("description");
+        representation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
+        representation.setLogic(Logic.NEGATIVE);
+        representation.addClient("Client A");
+        representation.addClient("Client B");
+
+        assertCreated(authorization, representation);
+    }
+
+    @Test
+    public void testUpdate() {
+        AuthorizationResource authorization = getClient().authorization();
+        ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
+
+        representation.setName("Update Test Client Policy");
+        representation.setDescription("description");
+        representation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
+        representation.setLogic(Logic.NEGATIVE);
+        representation.addClient("Client A");
+        representation.addClient("Client B");
+        representation.addClient("Client C");
+
+        assertCreated(authorization, representation);
+
+        representation.setName("changed");
+        representation.setDescription("changed");
+        representation.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
+        representation.setLogic(Logic.POSITIVE);
+        representation.setClients(representation.getClients().stream().filter(userName -> !userName.equals("Client A")).collect(Collectors.toSet()));
+
+        ClientPoliciesResource policies = authorization.policies().client();
+        ClientPolicyResource permission = policies.findById(representation.getId());
+
+        permission.update(representation);
+        assertRepresentation(representation, permission);
+
+        representation.setClients(representation.getClients().stream().filter(userName -> !userName.equals("Client C")).collect(Collectors.toSet()));
+
+        permission.update(representation);
+        assertRepresentation(representation, permission);
+    }
+
+    @Test
+    public void testDelete() {
+        AuthorizationResource authorization = getClient().authorization();
+        ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
+
+        representation.setName("Test Delete Permission");
+        representation.addClient("Client A");
+
+        ClientPoliciesResource policies = authorization.policies().client();
+        Response response = policies.create(representation);
+        ClientPolicyRepresentation created = response.readEntity(ClientPolicyRepresentation.class);
+
+        policies.findById(created.getId()).remove();
+
+        ClientPolicyResource removed = policies.findById(created.getId());
+
+        try {
+            removed.toRepresentation();
+            fail("Permission not removed");
+        } catch (NotFoundException ignore) {
+
+        }
+    }
+
+    @Test
+    public void testGenericConfig() {
+        AuthorizationResource authorization = getClient().authorization();
+        ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
+
+        representation.setName("Test Generic Config Permission");
+        representation.addClient("Client A");
+
+        ClientPoliciesResource policies = authorization.policies().client();
+        Response response = policies.create(representation);
+        ClientPolicyRepresentation created = response.readEntity(ClientPolicyRepresentation.class);
+
+        PolicyResource policy = authorization.policies().policy(created.getId());
+        PolicyRepresentation genericConfig = policy.toRepresentation();
+
+        assertNotNull(genericConfig.getConfig());
+        assertNotNull(genericConfig.getConfig().get("clients"));
+
+        ClientRepresentation user = getRealm().clients().findByClientId("Client A").get(0);
+
+        assertTrue(genericConfig.getConfig().get("clients").contains(user.getId()));
+    }
+
+    private void assertCreated(AuthorizationResource authorization, ClientPolicyRepresentation representation) {
+        ClientPoliciesResource permissions = authorization.policies().client();
+        Response response = permissions.create(representation);
+        ClientPolicyRepresentation created = response.readEntity(ClientPolicyRepresentation.class);
+        ClientPolicyResource permission = permissions.findById(created.getId());
+        assertRepresentation(representation, permission);
+    }
+
+    private void assertRepresentation(ClientPolicyRepresentation representation, ClientPolicyResource permission) {
+        ClientPolicyRepresentation actual = permission.toRepresentation();
+        assertRepresentation(representation, actual, () -> permission.resources(), () -> Collections.emptyList(), () -> permission.associatedPolicies());
+        assertEquals(representation.getClients().size(), actual.getClients().size());
+        assertEquals(0, actual.getClients().stream().filter(clientId -> !representation.getClients().stream()
+                .filter(userName -> getClientName(clientId).equalsIgnoreCase(userName))
+                .findFirst().isPresent())
+                .count());
+    }
+
+    private String getClientName(String id) {
+        return getRealm().clients().get(id).toRepresentation().getClientId();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ImportAuthorizationSettingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ImportAuthorizationSettingsTest.java
index 4b2168f..a308ec0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ImportAuthorizationSettingsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ImportAuthorizationSettingsTest.java
@@ -28,6 +28,7 @@ import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.testsuite.util.UserBuilder;
 import org.keycloak.util.JsonSerialization;
 
 /**
@@ -43,6 +44,8 @@ public class ImportAuthorizationSettingsTest extends AbstractAuthorizationTest {
         RoleRepresentation role = new RoleRepresentation();
         role.setName("admin");
         clientResource.roles().create(role);
+
+        testRealmResource().users().create(UserBuilder.create().username("alice").build());
     }
 
     @After
@@ -72,6 +75,6 @@ public class ImportAuthorizationSettingsTest extends AbstractAuthorizationTest {
 
         authorizationResource.importSettings(toImport);
 
-        assertEquals(13, authorizationResource.policies().policies().size());
+        assertEquals(15, authorizationResource.policies().policies().size());
     }
 }
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java
index c351ec3..f066c71 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/RolePolicyManagementTest.java
@@ -119,7 +119,7 @@ public class RolePolicyManagementTest extends AbstractPolicyManagementTest {
         representation.setLogic(Logic.POSITIVE);
         representation.setRoles(representation.getRoles().stream().filter(roleDefinition -> !roleDefinition.getId().equals("Resource A")).collect(Collectors.toSet()));
 
-        RolePoliciesResource policies = authorization.policies().roles();
+        RolePoliciesResource policies = authorization.policies().role();
         RolePolicyResource permission = policies.findById(representation.getId());
 
         permission.update(representation);
@@ -146,7 +146,7 @@ public class RolePolicyManagementTest extends AbstractPolicyManagementTest {
         representation.setName("Test Delete Permission");
         representation.addRole("Role A", false);
 
-        RolePoliciesResource policies = authorization.policies().roles();
+        RolePoliciesResource policies = authorization.policies().role();
         Response response = policies.create(representation);
         RolePolicyRepresentation created = response.readEntity(RolePolicyRepresentation.class);
 
@@ -170,7 +170,7 @@ public class RolePolicyManagementTest extends AbstractPolicyManagementTest {
         representation.setName("Test Generic Config  Permission");
         representation.addRole("Role A", false);
 
-        RolePoliciesResource policies = authorization.policies().roles();
+        RolePoliciesResource policies = authorization.policies().role();
         Response response = policies.create(representation);
         RolePolicyRepresentation created = response.readEntity(RolePolicyRepresentation.class);
 
@@ -186,7 +186,7 @@ public class RolePolicyManagementTest extends AbstractPolicyManagementTest {
     }
 
     private void assertCreated(AuthorizationResource authorization, RolePolicyRepresentation representation) {
-        RolePoliciesResource permissions = authorization.policies().roles();
+        RolePoliciesResource permissions = authorization.policies().role();
         Response response = permissions.create(representation);
         RolePolicyRepresentation created = response.readEntity(RolePolicyRepresentation.class);
         RolePolicyResource permission = permissions.findById(created.getId());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java
index 6628bca..b4f78e0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/UserPolicyManagementTest.java
@@ -54,7 +54,7 @@ public class UserPolicyManagementTest extends AbstractPolicyManagementTest {
     }
 
     @Test
-    public void testCreateUserPolicy() {
+    public void testCreate() {
         AuthorizationResource authorization = getClient().authorization();
         UserPolicyRepresentation representation = new UserPolicyRepresentation();
 
@@ -89,7 +89,7 @@ public class UserPolicyManagementTest extends AbstractPolicyManagementTest {
         representation.setLogic(Logic.POSITIVE);
         representation.setUsers(representation.getUsers().stream().filter(userName -> !userName.equals("User A")).collect(Collectors.toSet()));
 
-        UserPoliciesResource policies = authorization.policies().users();
+        UserPoliciesResource policies = authorization.policies().user();
         UserPolicyResource permission = policies.findById(representation.getId());
 
         permission.update(representation);
@@ -109,7 +109,7 @@ public class UserPolicyManagementTest extends AbstractPolicyManagementTest {
         representation.setName("Test Delete Permission");
         representation.addUser("User A");
 
-        UserPoliciesResource policies = authorization.policies().users();
+        UserPoliciesResource policies = authorization.policies().user();
         Response response = policies.create(representation);
         UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class);
 
@@ -133,7 +133,7 @@ public class UserPolicyManagementTest extends AbstractPolicyManagementTest {
         representation.setName("Test Generic Config Permission");
         representation.addUser("User A");
 
-        UserPoliciesResource policies = authorization.policies().users();
+        UserPoliciesResource policies = authorization.policies().user();
         Response response = policies.create(representation);
         UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class);
 
@@ -149,7 +149,7 @@ public class UserPolicyManagementTest extends AbstractPolicyManagementTest {
     }
 
     private void assertCreated(AuthorizationResource authorization, UserPolicyRepresentation representation) {
-        UserPoliciesResource permissions = authorization.policies().users();
+        UserPoliciesResource permissions = authorization.policies().user();
         Response response = permissions.create(representation);
         UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class);
         UserPolicyResource permission = permissions.findById(created.getId());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/import-authorization-unordered-settings.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/import-authorization-unordered-settings.json
index 47b7d1b..8bdb635 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/import-authorization-unordered-settings.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/import-authorization-unordered-settings.json
@@ -161,6 +161,20 @@
       "config": {
         "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": "Test Client Policy",
+      "type": "client",
+      "config": {
+        "clients": "[\"admin-cli\"]"
+      }
+    },
+    {
+      "name": "Test User Policy",
+      "type": "user",
+      "config": {
+        "users": "[\"alice\"]"
+      }
     }
   ],
   "scopes": [
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicy.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicy.java
new file mode 100644
index 0000000..fe85623
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicy.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.console.page.clients.authorization.policy;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClientPolicy implements PolicyTypeUI {
+
+    @Page
+    private ClientPolicyForm form;
+
+    public ClientPolicyForm form() {
+        return form;
+    }
+
+    public ClientPolicyRepresentation toRepresentation() {
+        return form.toRepresentation();
+    }
+
+    public void update(ClientPolicyRepresentation expected) {
+        form().populate(expected);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicyForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicyForm.java
new file mode 100644
index 0000000..9095a32
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicyForm.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.console.page.clients.authorization.policy;
+
+import static org.openqa.selenium.By.tagName;
+
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.testsuite.console.page.fragment.MultipleStringSelect2;
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClientPolicyForm extends Form {
+
+    @FindBy(id = "name")
+    private WebElement name;
+
+    @FindBy(id = "description")
+    private WebElement description;
+
+    @FindBy(id = "logic")
+    private Select logic;
+
+    @FindBy(xpath = "//i[contains(@class,'pficon-delete')]")
+    private WebElement deleteButton;
+
+    @FindBy(id = "s2id_clients")
+    private ClientSelect clientsInput;
+
+    @FindBy(xpath = ACTIVE_DIV_XPATH + "/button[text()='Delete']")
+    private WebElement confirmDelete;
+
+    public void populate(ClientPolicyRepresentation expected) {
+        setInputValue(name, expected.getName());
+        setInputValue(description, expected.getDescription());
+        logic.selectByValue(expected.getLogic().name());
+
+        clientsInput.update(expected.getClients());
+
+        save();
+    }
+
+    public void delete() {
+        deleteButton.click();
+        confirmDelete.click();
+    }
+
+    public ClientPolicyRepresentation toRepresentation() {
+        ClientPolicyRepresentation representation = new ClientPolicyRepresentation();
+
+        representation.setName(getInputValue(name));
+        representation.setDescription(getInputValue(description));
+        representation.setLogic(Logic.valueOf(logic.getFirstSelectedOption().getText().toUpperCase()));
+        representation.setClients(clientsInput.getSelected());
+
+        return representation;
+    }
+
+    public class ClientSelect extends MultipleStringSelect2 {
+
+        @Override
+        protected List<WebElement> getSelectedElements() {
+            return getRoot().findElements(By.xpath("(//table[@id='selected-clients'])/tbody/tr")).stream()
+                    .filter(webElement -> webElement.findElements(tagName("td")).size() > 1)
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        protected BiFunction<WebElement, String, Boolean> deselect() {
+            return (webElement, name) -> {
+                List<WebElement> tds = webElement.findElements(tagName("td"));
+
+                if (!tds.get(0).getText().isEmpty()) {
+                    if (tds.get(0).getText().equals(name)) {
+                        tds.get(1).findElement(By.tagName("button")).click();
+                        return true;
+                    }
+                }
+
+                return false;
+            };
+        }
+
+        @Override
+        protected Function<WebElement, String> representation() {
+            return webElement -> webElement.findElements(tagName("td")).get(0).getText();
+        }
+    }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java
index 37777aa..af2a540 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/Policies.java
@@ -21,6 +21,7 @@ import static org.openqa.selenium.By.tagName;
 import org.jboss.arquillian.graphene.page.Page;
 import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
 import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
@@ -62,6 +63,9 @@ public class Policies extends Form {
     @Page
     private RulePolicy rulePolicy;
 
+    @Page
+    private ClientPolicy clientPolicy;
+
     public PoliciesTable policies() {
         return table;
     }
@@ -95,6 +99,10 @@ public class Policies extends Form {
             rulePolicy.form().populate((RulePolicyRepresentation) expected);
             rulePolicy.form().save();
             return (P) rulePolicy;
+        } else if ("client".equals(type)) {
+            clientPolicy.form().populate((ClientPolicyRepresentation) expected);
+            clientPolicy.form().save();
+            return (P) clientPolicy;
         }
 
         return null;
@@ -120,6 +128,8 @@ public class Policies extends Form {
                     timePolicy.form().populate((TimePolicyRepresentation) representation);
                 } else if ("rules".equals(type)) {
                     rulePolicy.form().populate((RulePolicyRepresentation) representation);
+                } else if ("client".equals(type)) {
+                    clientPolicy.form().populate((ClientPolicyRepresentation) representation);
                 }
 
                 return;
@@ -146,6 +156,8 @@ public class Policies extends Form {
                     return (P) timePolicy;
                 } else if ("rules".equals(type)) {
                     return (P) rulePolicy;
+                } else if ("client".equals(type)) {
+                    return (P) clientPolicy;
                 }
             }
         }
@@ -173,6 +185,8 @@ public class Policies extends Form {
                     timePolicy.form().delete();
                 } else if ("rules".equals(type)) {
                     rulePolicy.form().delete();
+                } else if ("client".equals(type)) {
+                    clientPolicy.form().delete();
                 }
 
                 return;
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/AggregatePolicyManagementTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/AggregatePolicyManagementTest.java
index 2299200..f1bba0d 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/AggregatePolicyManagementTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/AggregatePolicyManagementTest.java
@@ -55,7 +55,7 @@ public class AggregatePolicyManagementTest extends AbstractAuthorizationSettings
 
         AuthorizationResource authorization = testRealmResource().clients().get(newClient.getId()).authorization();
         PoliciesResource policies = authorization.policies();
-        RolePoliciesResource roles = policies.roles();
+        RolePoliciesResource roles = policies.role();
 
         roles.create(policyA);
 
@@ -71,7 +71,7 @@ public class AggregatePolicyManagementTest extends AbstractAuthorizationSettings
         policyC.setName("Policy C");
         policyC.addUser("test");
 
-        policies.users().create(policyC);
+        policies.user().create(policyC);
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ClientPolicyManagementTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ClientPolicyManagementTest.java
new file mode 100644
index 0000000..2c95b83
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ClientPolicyManagementTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.console.authorization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
+import org.keycloak.representations.idm.authorization.Logic;
+import org.keycloak.testsuite.console.page.clients.authorization.policy.ClientPolicy;
+import org.keycloak.testsuite.util.ClientBuilder;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class ClientPolicyManagementTest extends AbstractAuthorizationSettingsTest {
+
+    @Before
+    public void configureTest() {
+        super.configureTest();
+        ClientsResource clients = testRealmResource().clients();
+        clients.create(ClientBuilder.create().clientId("client a").build());
+        clients.create(ClientBuilder.create().clientId("client b").build());
+        clients.create(ClientBuilder.create().clientId("client c").build());
+    }
+
+    @Test
+    public void testUpdate() throws InterruptedException {
+        authorizationPage.navigateTo();
+        ClientPolicyRepresentation expected = new ClientPolicyRepresentation();
+
+        expected.setName("Test Client Policy");
+        expected.setDescription("description");
+        expected.addClient("client a");
+        expected.addClient("client b");
+        expected.addClient("client c");
+
+        expected = createPolicy(expected);
+
+        String previousName = expected.getName();
+
+        expected.setName("Changed Test Client Policy");
+        expected.setDescription("Changed description");
+        expected.setLogic(Logic.NEGATIVE);
+
+        expected.setClients(expected.getClients().stream().filter(client -> !client.equals("client b")).collect(Collectors.toSet()));
+
+        authorizationPage.navigateTo();
+        authorizationPage.authorizationTabs().policies().update(previousName, expected);
+        assertAlertSuccess();
+
+        authorizationPage.navigateTo();
+        ClientPolicy actual = authorizationPage.authorizationTabs().policies().name(expected.getName());
+
+        assertPolicy(expected, actual);
+    }
+
+    @Test
+    public void testDeletePolicy() throws InterruptedException {
+        authorizationPage.navigateTo();
+        ClientPolicyRepresentation expected = new ClientPolicyRepresentation();
+
+        expected.setName("Test Client Policy");
+        expected.setDescription("description");
+        expected.addClient("client c");
+
+        expected = createPolicy(expected);
+        authorizationPage.navigateTo();
+        authorizationPage.authorizationTabs().policies().delete(expected.getName());
+        assertAlertSuccess();
+        authorizationPage.navigateTo();
+        assertNull(authorizationPage.authorizationTabs().policies().policies().findByName(expected.getName()));
+    }
+
+    private ClientPolicyRepresentation createPolicy(ClientPolicyRepresentation expected) {
+        ClientPolicy policy = authorizationPage.authorizationTabs().policies().create(expected);
+        assertAlertSuccess();
+        return assertPolicy(expected, policy);
+    }
+
+    private ClientPolicyRepresentation assertPolicy(ClientPolicyRepresentation expected, ClientPolicy policy) {
+        ClientPolicyRepresentation actual = policy.toRepresentation();
+
+        assertEquals(expected.getName(), actual.getName());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.getLogic(), actual.getLogic());
+
+        assertNotNull(actual.getClients());
+        assertEquals(expected.getClients().size(), actual.getClients().size());
+        assertEquals(0, actual.getClients().stream().filter(actualClient -> !expected.getClients().stream()
+                .filter(expectedClient -> actualClient.equals(expectedClient))
+                .findFirst().isPresent())
+                .count());
+        return actual;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ResourcePermissionManagementTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ResourcePermissionManagementTest.java
index 7eb6a94..4ff011a 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ResourcePermissionManagementTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ResourcePermissionManagementTest.java
@@ -56,7 +56,7 @@ public class ResourcePermissionManagementTest extends AbstractAuthorizationSetti
 
         AuthorizationResource authorization = testRealmResource().clients().get(newClient.getId()).authorization();
         PoliciesResource policies = authorization.policies();
-        RolePoliciesResource roles = policies.roles();
+        RolePoliciesResource roles = policies.role();
 
         roles.create(policyA);
 
@@ -72,7 +72,7 @@ public class ResourcePermissionManagementTest extends AbstractAuthorizationSetti
         policyC.setName("Policy C");
         policyC.addUser("test");
 
-        policies.users().create(policyC);
+        policies.user().create(policyC);
 
         ResourcesResource resources = authorization.resources();
 
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ScopePermissionManagementTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ScopePermissionManagementTest.java
index 7852c20..3dfd0c8 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ScopePermissionManagementTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ScopePermissionManagementTest.java
@@ -55,7 +55,7 @@ public class ScopePermissionManagementTest extends AbstractAuthorizationSettings
 
         AuthorizationResource authorization = testRealmResource().clients().get(newClient.getId()).authorization();
         PoliciesResource policies = authorization.policies();
-        RolePoliciesResource roles = policies.roles();
+        RolePoliciesResource roles = policies.role();
 
         roles.create(policyA);
 
@@ -71,7 +71,7 @@ public class ScopePermissionManagementTest extends AbstractAuthorizationSettings
         policyC.setName("Policy C");
         policyC.addUser("test");
 
-        policies.users().create(policyC);
+        policies.user().create(policyC);
 
         authorization.scopes().create(new ScopeRepresentation("Scope A"));
         authorization.scopes().create(new ScopeRepresentation("Scope B"));
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 aed7cbb..5d8d462 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
@@ -1434,8 +1434,8 @@ module.controller('ResourceServerPolicyClientDetailCtrl', function($scope, $rout
         onInitUpdate : function(policy) {
             var selectedClients = [];
 
-            if (policy.config.clients) {
-                var clients = eval(policy.config.clients);
+            if (policy.clients) {
+                var clients = policy.clients;
 
                 for (var i = 0; i < clients.length; i++) {
                     Client.get({realm: $route.current.params.realm, client: clients[i]}, function(data) {
@@ -1461,7 +1461,8 @@ module.controller('ResourceServerPolicyClientDetailCtrl', function($scope, $rout
                 clients.push($scope.selectedClients[i].id);
             }
 
-            $scope.policy.config.clients = JSON.stringify(clients);
+            $scope.policy.clients = clients;
+            delete $scope.policy.config;
         },
 
         onInitCreate : function() {
@@ -1481,7 +1482,8 @@ module.controller('ResourceServerPolicyClientDetailCtrl', function($scope, $rout
                 clients.push($scope.selectedClients[i].id);
             }
 
-            $scope.policy.config.clients = JSON.stringify(clients);
+            $scope.policy.clients = clients;
+            delete $scope.policy.config;
         }
     }, realm, client, $scope);
 });
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html
index dda34a2..de2da05 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html
@@ -42,7 +42,7 @@
             <div class="form-group clearfix" style="margin-top: -15px;">
                 <label class="col-md-2 control-label"></label>
                 <div class="col-sm-3">
-                    <table class="table table-striped table-bordered">
+                    <table class="table table-striped table-bordered" id="selected-clients">
                         <thead>
                         <tr data-ng-hide="!selectedClients.length">
                             <th>{{:: 'clientId' | translate}}</th>
@@ -64,10 +64,10 @@
                 </div>
             </div>
             <div class="form-group clearfix">
-                <label class="col-md-2 control-label" for="policy.logic">{{:: 'authz-policy-logic' | translate}}</label>
+                <label class="col-md-2 control-label" for="logic">{{:: 'authz-policy-logic' | translate}}</label>
 
                 <div class="col-sm-1">
-                    <select class="form-control" id="policy.logic"
+                    <select class="form-control" id="logic"
                             data-ng-model="policy.logic">
                         <option value="POSITIVE">{{:: 'authz-policy-logic-positive' | translate}}</option>
                         <option value="NEGATIVE">{{:: 'authz-policy-logic-negative' | translate}}</option>