/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.authorization;
import org.apache.commons.collections.map.HashedMap;
import org.junit.Test;
import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest {
@Test
public void testCreateForTypeWithSinglePolicy() throws Exception {
PolicyRepresentation newPermission = new PolicyRepresentation();
newPermission.setName("Admin Resource Policy");
newPermission.setType("resource");
HashedMap config = new HashedMap();
config.put("defaultResourceType", "http://photoz.com/admin");
config.put("applyPolicies", JsonSerialization.writeValueAsString(new String[] {this.administrationPolicy.getId()}));
newPermission.setConfig(config);
Response response = newPermissionRequest().post(Entity.entity(newPermission, MediaType.APPLICATION_JSON_TYPE));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class);
onAuthorizationSession(authorizationProvider -> {
Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId());
assertNotNull(policyModel);
assertEquals(permission.getId(), policyModel.getId());
assertEquals(newPermission.getName(), policyModel.getName());
assertEquals(newPermission.getType(), policyModel.getType());
assertEquals(resourceServer.getId(), policyModel.getResourceServer().getId());
});
Set<String> roles = new HashSet<>();
roles.add("admin");
Map<String, DefaultEvaluation> evaluationsAdminRole = performEvaluation(
Arrays.asList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluationsAdminRole.size());
assertTrue(evaluationsAdminRole.containsKey(this.administrationPolicy.getId()));
assertEquals(Effect.PERMIT, evaluationsAdminRole.get(this.administrationPolicy.getId()).getEffect());
evaluationsAdminRole = performEvaluation(
Arrays.asList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.10"));
assertEquals(1, evaluationsAdminRole.size());
assertTrue(evaluationsAdminRole.containsKey(this.administrationPolicy.getId()));
assertEquals(Effect.DENY, evaluationsAdminRole.get(this.administrationPolicy.getId()).getEffect());
roles.clear();
roles.add("user");
Map<String, DefaultEvaluation> evaluationsUserRole = performEvaluation(
Arrays.asList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluationsUserRole.size());
assertTrue(evaluationsUserRole.containsKey(this.administrationPolicy.getId()));
assertEquals(Effect.DENY, evaluationsUserRole.get(this.administrationPolicy.getId()).getEffect());
}
@Test
public void testCreateForTypeWithMultiplePolicies() throws Exception {
createAlbumResourceTypePermission();
HashSet<String> roles = new HashSet<>();
roles.add("admin");
Map<String, DefaultEvaluation> evaluationsAdminRole = performEvaluation(
Arrays.asList(new ResourcePermission(albumResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
assertEquals(2, evaluationsAdminRole.size());
assertTrue(evaluationsAdminRole.containsKey(this.administrationPolicy.getId()));
assertTrue(evaluationsAdminRole.containsKey(this.anyUserPolicy.getId()));
assertEquals(Effect.DENY, evaluationsAdminRole.get(this.anyUserPolicy.getId()).getEffect());
assertEquals(Effect.PERMIT, evaluationsAdminRole.get(this.administrationPolicy.getId()).getEffect());
evaluationsAdminRole = performEvaluation(
Arrays.asList(new ResourcePermission(albumResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.10"));
assertEquals(2, evaluationsAdminRole.size());
assertTrue(evaluationsAdminRole.containsKey(this.administrationPolicy.getId()));
assertTrue(evaluationsAdminRole.containsKey(this.anyUserPolicy.getId()));
assertEquals(Effect.DENY, evaluationsAdminRole.get(this.anyUserPolicy.getId()).getEffect());
assertEquals(Effect.DENY, evaluationsAdminRole.get(this.administrationPolicy.getId()).getEffect());
roles.clear();
roles.add("user");
Map<String, DefaultEvaluation> evaluationsUserRole = performEvaluation(
Arrays.asList(new ResourcePermission(albumResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
assertEquals(2, evaluationsUserRole.size());
assertTrue(evaluationsUserRole.containsKey(this.administrationPolicy.getId()));
assertTrue(evaluationsUserRole.containsKey(this.anyUserPolicy.getId()));
assertEquals(Effect.PERMIT, evaluationsUserRole.get(this.anyUserPolicy.getId()).getEffect());
assertEquals(Effect.DENY, evaluationsUserRole.get(this.administrationPolicy.getId()).getEffect());
}
@Test
public void testUpdate() throws Exception {
PolicyRepresentation permission = createAlbumResourceTypePermission();
Map<String, String> config = permission.getConfig();
config.put("applyPolicies", JsonSerialization.writeValueAsString(new String[] {this.anyUserPolicy.getId()}));
permission.setConfig(config);
newPermissionRequest(permission.getId()).put(Entity.entity(permission, MediaType.APPLICATION_JSON_TYPE));
HashSet<String> roles = new HashSet<>();
roles.add("admin");
Map<String, DefaultEvaluation> evaluationsAdminRole = performEvaluation(
Arrays.asList(new ResourcePermission(albumResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluationsAdminRole.size());
assertTrue(evaluationsAdminRole.containsKey(this.anyUserPolicy.getId()));
assertEquals(Effect.DENY, evaluationsAdminRole.get(this.anyUserPolicy.getId()).getEffect());
}
@Test
public void testDelete() throws Exception {
PolicyRepresentation newPermission = createAlbumResourceTypePermission();
Response delete = newPermissionRequest(newPermission.getId()).delete();
assertEquals(Status.NO_CONTENT.getStatusCode(), delete.getStatus());
}
@Test
public void testFindById() throws Exception {
PolicyRepresentation newPermission = createAlbumResourceTypePermission();
Response response = newPermissionRequest(newPermission.getId()).get();
PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class);
assertEquals(newPermission.getId(), permission.getId());
assertEquals(newPermission.getName(), permission.getName());
assertEquals(newPermission.getType(), permission.getType());
}
@Test
public void testCreatePolicyForResource() throws Exception {
PolicyRepresentation newPermission = new PolicyRepresentation();
newPermission.setName("Multiple Resource Policy");
newPermission.setType("resource");
HashedMap config = new HashedMap();
config.put("resources", JsonSerialization.writeValueAsString(new String[] {this.albumResource.getId(), this.adminResource.getId()}));
config.put("applyPolicies", JsonSerialization.writeValueAsString(new String[] {this.onlyFromSpecificAddressPolicy.getId()}));
newPermission.setConfig(config);
Response response = newPermissionRequest().post(Entity.entity(newPermission, MediaType.APPLICATION_JSON_TYPE));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
List<ResourcePermission> permissions = new ArrayList<>();
permissions.add(new ResourcePermission(this.albumResource, Collections.emptyList(), this.resourceServer));
Map<String, DefaultEvaluation> evaluations = performEvaluation(
permissions,
createAccessToken(Collections.emptySet()),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluations.size());
assertTrue(evaluations.containsKey(this.onlyFromSpecificAddressPolicy.getId()));
assertEquals(Effect.PERMIT, evaluations.get(this.onlyFromSpecificAddressPolicy.getId()).getEffect());
permissions = new ArrayList<>();
permissions.add(new ResourcePermission(this.adminResource, Collections.emptyList(), this.resourceServer));
evaluations = performEvaluation(
permissions,
createAccessToken(Collections.emptySet()),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluations.size());
assertTrue(evaluations.containsKey(this.onlyFromSpecificAddressPolicy.getId()));
assertEquals(Effect.PERMIT, evaluations.get(this.onlyFromSpecificAddressPolicy.getId()).getEffect());
permissions = new ArrayList<>();
permissions.add(new ResourcePermission(this.adminResource, Collections.emptyList(), this.resourceServer));
permissions.add(new ResourcePermission(this.albumResource, Collections.emptyList(), this.resourceServer));
evaluations = performEvaluation(
permissions,
createAccessToken(Collections.emptySet()),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluations.size());
assertTrue(evaluations.containsKey(this.onlyFromSpecificAddressPolicy.getId()));
assertEquals(Effect.PERMIT, evaluations.get(this.onlyFromSpecificAddressPolicy.getId()).getEffect());
permissions = new ArrayList<>();
permissions.add(new ResourcePermission(this.adminResource, Collections.emptyList(), this.resourceServer));
permissions.add(new ResourcePermission(this.albumResource, Collections.emptyList(), this.resourceServer));
evaluations = performEvaluation(
permissions,
createAccessToken(Collections.emptySet()),
createClientConnection("127.0.0.10"));
assertEquals(1, evaluations.size());
assertTrue(evaluations.containsKey(this.onlyFromSpecificAddressPolicy.getId()));
assertEquals(Effect.DENY, evaluations.get(this.onlyFromSpecificAddressPolicy.getId()).getEffect());
}
/**
* Tests if a resource can inherit the policies defined for another resource based on its type
*
* @throws Exception
*/
@Test
public void testInheritPoliciesBasedOnResourceType() throws Exception {
createAlbumResourceTypePermission();
ResourceRepresentation representation = new ResourceRepresentation();
representation.setName("Alice Family Album");
representation.setType(this.albumResource.getType());
Resource resource = createResource(representation);
Set<String> roles = new HashSet<>();
roles.add("user");
Map<String, DefaultEvaluation> evaluationsUserRole = performEvaluation(
Arrays.asList(new ResourcePermission(resource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
assertEquals(2, evaluationsUserRole.size());
assertTrue(evaluationsUserRole.containsKey(this.administrationPolicy.getId()));
assertTrue(evaluationsUserRole.containsKey(this.anyUserPolicy.getId()));
assertEquals(Effect.PERMIT, evaluationsUserRole.get(this.anyUserPolicy.getId()).getEffect());
assertEquals(Effect.DENY, evaluationsUserRole.get(this.administrationPolicy.getId()).getEffect());
ResourceRepresentation someResourceRep = new ResourceRepresentation();
someResourceRep.setName("Some Resource");
someResourceRep.setType("Some non-existent type");
Resource someResource = createResource(someResourceRep);
evaluationsUserRole = performEvaluation(
Arrays.asList(new ResourcePermission(someResource, Collections.emptyList(), resourceServer)),
createAccessToken(roles),
createClientConnection("127.0.0.1"));
// no policies can be applied given that there is no policy defined for this resource or its type
assertEquals(0, evaluationsUserRole.size());
}
@Test
public void testResourceAccessWithClientBasedPolicy() throws Exception {
ClientModel testClient1 = getClientByClientId("test-client-1");
ClientModel testClient2 = getClientByClientId("test-client-2");
Policy clientPolicy = createClientPolicy(Collections.singletonList(testClient1));
PolicyRepresentation newPermission = new PolicyRepresentation();
newPermission.setName("Client Permission");
newPermission.setType("resource");
HashedMap config = new HashedMap();
config.put("defaultResourceType", "http://photoz.com/admin");
config.put("applyPolicies", JsonSerialization.writeValueAsString(new String[] {clientPolicy.getId()}));
newPermission.setConfig(config);
Response response = newPermissionRequest().post(Entity.entity(newPermission, MediaType.APPLICATION_JSON_TYPE));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class);
onAuthorizationSession(authorizationProvider -> {
Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId());
assertNotNull(policyModel);
assertEquals(permission.getId(), policyModel.getId());
assertEquals(newPermission.getName(), policyModel.getName());
assertEquals(resourceServer.getId(), policyModel.getResourceServer().getId());
});
Map<String, DefaultEvaluation> evaluations = performEvaluation(
Collections.singletonList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
createAccessTokenForClient(testClient1),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluations.size());
assertTrue(evaluations.containsKey(clientPolicy.getId()));
assertEquals(Effect.PERMIT, evaluations.get(clientPolicy.getId()).getEffect());
Map<String, DefaultEvaluation> evaluations2 = performEvaluation(
Collections.singletonList(new ResourcePermission(adminResource, Collections.emptyList(), resourceServer)),
createAccessTokenForClient(testClient2),
createClientConnection("127.0.0.1"));
assertEquals(1, evaluations2.size());
assertTrue(evaluations2.containsKey(clientPolicy.getId()));
assertEquals(Effect.DENY, evaluations2.get(clientPolicy.getId()).getEffect());
}
private Policy createClientPolicy(List<ClientModel> allowedClients) {
return onAuthorizationSession(authorizationProvider -> {
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
PolicyStore policyStore = storeFactory.getPolicyStore();
PolicyRepresentation representation = new PolicyRepresentation();
representation.setName("Client-Based Policy");
representation.setType("client");
List<String> clientIds = new ArrayList<>();
for (ClientModel client : allowedClients) {
clientIds.add(client.getId());
}
String[] clients = clientIds.toArray(new String[clientIds.size()]);
HashedMap config = new HashedMap();
try {
config.put("clients", JsonSerialization.writeValueAsString(clients));
} catch (IOException e) {
throw new RuntimeException(e);
}
representation.setConfig(config);
return policyStore.create(representation, resourceServer);
});
}
private AccessToken createAccessTokenForClient(ClientModel client) {
AccessToken accessToken = new AccessToken();
accessToken.setRealmAccess(new AccessToken.Access());
accessToken.issuedFor = client.getClientId();
return accessToken;
}
private PolicyRepresentation createAlbumResourceTypePermission() throws Exception {
PolicyRepresentation newPermission = new PolicyRepresentation();
newPermission.setName("Album Resource Policy");
newPermission.setType("resource");
newPermission.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
HashedMap config = new HashedMap();
config.put("defaultResourceType", albumResource.getType());
String[] associatedPolicies = {this.anyUserPolicy.getId(), this.administrationPolicy.getId()};
String applyPolicies = JsonSerialization.writeValueAsString(associatedPolicies);
config.put("applyPolicies", applyPolicies);
newPermission.setConfig(config);
Response response = newPermissionRequest().post(Entity.entity(newPermission, MediaType.APPLICATION_JSON_TYPE));
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class);
onAuthorizationSession(authorizationProvider -> {
Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId());
assertNotNull(policyModel);
assertEquals(permission.getId(), policyModel.getId());
assertEquals(permission.getName(), policyModel.getName());
assertEquals(permission.getType(), policyModel.getType());
assertTrue(permission.getConfig().containsValue(albumResource.getType()));
assertTrue(policyModel.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toList()).containsAll(Arrays.asList(associatedPolicies)));
assertEquals(resourceServer.getId(), policyModel.getResourceServer().getId());
});
return permission;
}
}