From fbcfcfa088de5ddf82978ee5cbea6336d04261be Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 26 Apr 2017 12:11:53 -0300 Subject: [PATCH] [KEYCLOAK-4755] - Client UI Tests --- .../provider/client/ClientPolicyProvider.java | 27 +-- .../client/ClientPolicyProviderFactory.java | 73 +++++++- .../provider/role/RolePolicyProvider.java | 40 ++-- .../role/RolePolicyProviderFactory.java | 96 +++++----- .../provider/user/UserPolicyProvider.java | 24 ++- .../user/UserPolicyProviderFactory.java | 57 +++--- .../ClientPolicyRepresentation.java | 49 +++++ .../resource/ClientPoliciesResource.java | 50 +++++ .../client/resource/ClientPolicyResource.java | 69 +++++++ .../client/resource/PoliciesResource.java | 7 +- .../AbstractServletAuthzAdapterTest.java | 4 +- ...ractServletAuthzFunctionalAdapterTest.java | 103 ++++++++++- .../AbstractPolicyManagementTest.java | 2 +- .../ClientPolicyManagementTest.java | 172 ++++++++++++++++++ .../ImportAuthorizationSettingsTest.java | 5 +- .../RolePolicyManagementTest.java | 8 +- .../UserPolicyManagementTest.java | 10 +- ...port-authorization-unordered-settings.json | 14 ++ .../authorization/policy/ClientPolicy.java | 41 +++++ .../policy/ClientPolicyForm.java | 114 ++++++++++++ .../authorization/policy/Policies.java | 14 ++ .../AggregatePolicyManagementTest.java | 4 +- .../ClientPolicyManagementTest.java | 116 ++++++++++++ .../ResourcePermissionManagementTest.java | 4 +- .../ScopePermissionManagementTest.java | 4 +- .../resources/js/authz/authz-controller.js | 10 +- .../resource-server-policy-client-detail.html | 6 +- 27 files changed, 965 insertions(+), 158 deletions(-) create mode 100644 core/src/main/java/org/keycloak/representations/idm/authorization/ClientPolicyRepresentation.java create mode 100644 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPoliciesResource.java create mode 100644 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientPolicyResource.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/ClientPolicyManagementTest.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicy.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/authorization/policy/ClientPolicyForm.java create mode 100644 testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authorization/ClientPolicyManagementTest.java 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 5c778d810c3..ffd92bf7056 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 representationFunction; + + public ClientPolicyProvider(Function 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(); - 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; - } + for (String client : representation.getClients()) { + ClientModel clientModel = realm.getClientById(client); + + 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 a0e78746a5e..d0613572182 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 { - 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 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 clients, AuthorizationProvider authorization) { + RealmModel realm = authorization.getKeycloakSession().getContext().getRealm(); + + if (clients == null || clients.isEmpty()) { + throw new RuntimeException("No client provided."); + } + + Set 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 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 4aafedd9e4f..e88eed66653 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 Pedro Igor */ public class RolePolicyProvider implements PolicyProvider { + private final Function representationFunction; + + public RolePolicyProvider(Function representationFunction) { + this.representationFunction = representationFunction; + } + @Override public void evaluate(Evaluation evaluation) { Policy policy = evaluation.getPolicy(); - Map[] roleIds = getRoles(policy); + Set 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 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 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 d5d39175da5..03ea156798a 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 { - private RolePolicyProvider provider = new RolePolicyProvider(); + private RolePolicyProvider provider = new RolePolicyProvider(policy -> toRepresentation(policy, new RolePolicyRepresentation())); @Override public String getName() { @@ -70,20 +69,15 @@ public class RolePolicyProviderFactory implements PolicyProviderFactory(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 roles) { - try { - RealmModel realm = authorization.getRealm(); - Set updatedRoles = new HashSet<>(); + RealmModel realm = authorization.getRealm(); + Set updatedRoles = new HashSet<>(); - if (roles != null) { - for (RolePolicyRepresentation.RoleDefinition definition : roles) { - String roleName = definition.getId(); - String clientId = null; - int clientIdSeparator = roleName.indexOf("/"); + 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 (clientIdSeparator != -1) { + clientId = roleName.substring(0, clientIdSeparator); + roleName = roleName.substring(clientIdSeparator + 1); + } - RoleModel role; + RoleModel role; - if (clientId == null) { - role = realm.getRole(roleName); - - if (role == null) { - role = realm.getRoleById(roleName); - } - } else { - ClientModel client = realm.getClientByClientId(clientId); - - if (client == null) { - throw new RuntimeException("Client with id [" + clientId + "] not found."); - } - - role = client.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); - } + if (clientId == null) { + role = realm.getRole(roleName); if (role == null) { - throw new RuntimeException("Error while importing configuration. Role [" + roleName + "] could not be found."); + role = realm.getRoleById(roleName); + } + } else { + ClientModel client = realm.getClientByClientId(clientId); + + 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 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[] getRoles(Policy policy) { + private Map[] 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 2f771065040..f891257079e 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 Pedro Igor */ public class UserPolicyProvider implements PolicyProvider { + private final Function representationFunction; + + public UserPolicyProvider(Function representationFunction) { + this.representationFunction = representationFunction; + } + @Override public void evaluate(Evaluation evaluation) { - Policy policy = evaluation.getPolicy(); EvaluationContext context = evaluation.getContext(); - String[] userIds = getUsers(policy); + UserPolicyRepresentation representation = representationFunction.apply(evaluation.getPolicy()); - if (userIds.length > 0) { - for (String userId : userIds) { - if (context.getIdentity().getId().equals(userId)) { - evaluation.grant(); - break; - } + 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 0fd54df2b11..e4bbaf9900c 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 { - private UserPolicyProvider provider = new UserPolicyProvider(); + private UserPolicyProvider provider = new UserPolicyProvider((Function) policy -> toRepresentation(policy, new UserPolicyRepresentation())); @Override public String getName() { @@ -110,42 +111,40 @@ public class UserPolicyProviderFactory implements PolicyProviderFactory users) { - try { - KeycloakSession session = authorization.getKeycloakSession(); - RealmModel realm = authorization.getRealm(); - UserProvider userProvider = session.users(); - Set updatedUsers = new HashSet<>(); + KeycloakSession session = authorization.getKeycloakSession(); + RealmModel realm = authorization.getRealm(); + UserProvider userProvider = session.users(); + Set updatedUsers = new HashSet<>(); + + if (users != null) { + for (String userId : users) { + UserModel user = null; - if (users != null) { try { - for (String userId : users) { - UserModel user = null; - - try { - user = userProvider.getUserByUsername(userId, realm); - } catch (Exception ignore) { - } - - if (user == null) { - user = userProvider.getUserById(userId, realm); - } - - if (user == null) { - throw new RuntimeException("Error while importing configuration. User [" + userId + "] could not be found."); - } - - updatedUsers.add(user.getId()); - } - } catch (Exception e) { - throw new RuntimeException("Error while updating policy [" + policy.getName() + "].", e); + user = userProvider.getUserByUsername(userId, realm); + } catch (Exception ignore) { } - } + if (user == null) { + user = userProvider.getUserById(userId, realm); + } + + if (user == null) { + throw new RuntimeException("Error while updating policy [" + policy.getName() + "]. User [" + userId + "] could not be found."); + } + + updatedUsers.add(user.getId()); + } + } + + try { Map 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 00000000000..d922a8f3fe9 --- /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 Pedro Igor + */ +public class ClientPolicyRepresentation extends AbstractPolicyRepresentation { + + private Set clients; + + @Override + public String getType() { + return "client"; + } + + public Set getClients() { + return clients; + } + + public void setClients(Set 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 00000000000..3fd7778c49f --- /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 Pedro Igor + */ +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 00000000000..a9449358c4e --- /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 Pedro Igor + */ +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 associatedPolicies(); + + @Path("/dependentPolicies") + @GET + @Produces(MediaType.APPLICATION_JSON) + @NoCache + List dependentPolicies(); + + @Path("/resources") + @GET + @Produces("application/json") + @NoCache + List 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 4ce362dbb50..a0af5d4c62a 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 d61b077b215..5c3f1f1e60a 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 3aef53778e5..b37e9e60fb4 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 b1f0b2472ae..e0a4c53460f 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 00000000000..87848d911ce --- /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 Pedro Igor + */ +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 4b2168f0d11..a308ec0b331 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 c351ec37cdf..f066c713865 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 6628bca3ab9..b4f78e08b46 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 47b7d1b9a9a..8bdb6352b10 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 00000000000..fe856239284 --- /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 Pedro Igor + */ +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 00000000000..9095a3207db --- /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 Pedro Igor + */ +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 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 deselect() { + return (webElement, name) -> { + List 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 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 37777aa2310..af2a5402bd9 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 22992003073..f1bba0d6eb5 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 00000000000..2c95b83896b --- /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 Pedro Igor + */ +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 7eb6a944b70..4ff011a6d04 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 7852c20b5f2..3dfd0c87ef5 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 aed7cbb744c..5d8d4629cfb 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 dda34a2ae71..de2da055fa1 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 @@
- +
@@ -64,10 +64,10 @@
- +
-
{{:: 'clientId' | translate}}