Remove permissions and resources when their corresponding objects are deleted

Closes #37242

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2025-03-10 13:59:13 +01:00 committed by Pedro Igor
parent fde8cc5944
commit 872a691757
15 changed files with 559 additions and 37 deletions

View File

@ -16,6 +16,9 @@
*/
package org.keycloak.authorization;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -31,24 +34,32 @@ import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.common.Profile;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientModel.ClientRemovedEvent;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupModel.GroupRemovedEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.UserRemovedEvent;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationSchema;
import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceType;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.util.JsonSerialization;
public class AdminPermissionsSchema extends AuthorizationSchema {
@ -356,18 +367,10 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
if (supportsAuthorizationSchema(session, resourceServer)) {
switch (resourceType) {
case CLIENTS_RESOURCE_TYPE -> {
return resolveClient(session, resourceName).map(ClientModel::getClientId).orElse(resourceType);
}
case GROUPS_RESOURCE_TYPE -> {
return resolveGroup(session, resourceName).map(GroupModel::getName).orElse(resourceType);
}
case ROLES_RESOURCE_TYPE -> {
return resolveRole(session, resourceName).map(RoleModel::getName).orElse(resourceType);
}
case USERS_RESOURCE_TYPE -> {
return resolveUser(session, resourceName).map(UserModel::getUsername).orElse(resourceType);
}
case CLIENTS_RESOURCE_TYPE -> resolveClient(session, resourceName).map(ClientModel::getClientId).orElse(resourceType);
case GROUPS_RESOURCE_TYPE -> resolveGroup(session, resourceName).map(GroupModel::getName).orElse(resourceType);
case ROLES_RESOURCE_TYPE -> resolveRole(session, resourceName).map(RoleModel::getName).orElse(resourceType);
case USERS_RESOURCE_TYPE -> resolveUser(session, resourceName).map(UserModel::getUsername).orElse(resourceType);
default -> throw new IllegalStateException("Resource type [" + resourceType + "] not found.");
}
}
@ -388,4 +391,39 @@ public class AdminPermissionsSchema extends AuthorizationSchema {
}
}
}
public void removeResourceObject(AuthorizationProvider authorization, ProviderEvent event) {
if (!isAdminPermissionsEnabled(authorization.getRealm()) || authorization.getRealm().getAdminPermissionsClient() == null) return;
String id;
if (event instanceof UserRemovedEvent userRemovedEvent) {
id = userRemovedEvent.getUser().getId();
} else if (event instanceof ClientRemovedEvent clientRemovedEvent) {
id = clientRemovedEvent.getClient().getId();
} else if (event instanceof GroupRemovedEvent groupRemovedEvent) {
id = groupRemovedEvent.getGroup().getId();
} else if (event instanceof RoleRemovedEvent roleRemovedEvent) {
id = roleRemovedEvent.getRole().getId();
} else {
return;
}
ResourceServer server = authorization.getStoreFactory().getResourceServerStore().findByClient(authorization.getRealm().getAdminPermissionsClient());
Resource resource = authorization.getStoreFactory().getResourceStore().findByName(server, id);
if (resource != null) {
List<Policy> permissions = authorization.getStoreFactory().getPolicyStore().findByResource(server, resource);
//remove object from permission if there is more than one resource, remove the permission if there is only the removed object
for (Policy permission : permissions) {
if (permission.getResources().size() == 1) {
authorization.getStoreFactory().getPolicyStore().delete(permission.getId());
} else {
permission.removeResource(resource);
}
}
//remove the resource associated with the object
authorization.getStoreFactory().getResourceStore().delete(resource.getId());
}
}
}

View File

@ -22,12 +22,16 @@ import java.util.HashMap;
import java.util.Map;
import org.keycloak.authorization.store.syncronization.ClientApplicationSynchronizer;
import org.keycloak.authorization.store.syncronization.GroupSynchronizer;
import org.keycloak.authorization.store.syncronization.RealmSynchronizer;
import org.keycloak.authorization.store.syncronization.RoleSynchronizer;
import org.keycloak.authorization.store.syncronization.Synchronizer;
import org.keycloak.authorization.store.syncronization.UserSynchronizer;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ClientModel.ClientRemovedEvent;
import org.keycloak.models.GroupModel.GroupRemovedEvent;
import org.keycloak.models.RealmModel.RealmRemovedEvent;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.models.UserModel.UserRemovedEvent;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderFactory;
@ -48,6 +52,8 @@ public interface AuthorizationStoreFactory extends ProviderFactory<StoreFactory>
synchronizers.put(ClientRemovedEvent.class, new ClientApplicationSynchronizer());
synchronizers.put(RealmRemovedEvent.class, new RealmSynchronizer());
synchronizers.put(UserRemovedEvent.class, new UserSynchronizer());
synchronizers.put(GroupRemovedEvent.class, new GroupSynchronizer());
synchronizers.put(RoleRemovedEvent.class, new RoleSynchronizer());
factory.register(event -> {
try {

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
@ -43,6 +44,8 @@ public class ClientApplicationSynchronizer implements Synchronizer<ClientRemoved
ProviderFactory<AuthorizationProvider> providerFactory = factory.getProviderFactory(AuthorizationProvider.class);
AuthorizationProvider authorizationProvider = providerFactory.create(event.getKeycloakSession());
AdminPermissionsSchema.SCHEMA.removeResourceObject(authorizationProvider, event);
removeFromClientPolicies(event, authorizationProvider);
}

View File

@ -0,0 +1,18 @@
package org.keycloak.authorization.store.syncronization;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.models.GroupModel.GroupRemovedEvent;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderFactory;
public class GroupSynchronizer implements Synchronizer<GroupRemovedEvent> {
@Override
public void synchronize(GroupRemovedEvent event, KeycloakSessionFactory factory) {
ProviderFactory<AuthorizationProvider> providerFactory = factory.getProviderFactory(AuthorizationProvider.class);
AuthorizationProvider authorizationProvider = providerFactory.create(event.getKeycloakSession());
AdminPermissionsSchema.SCHEMA.removeResourceObject(authorizationProvider, event);
}
}

View File

@ -0,0 +1,18 @@
package org.keycloak.authorization.store.syncronization;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.provider.ProviderFactory;
public class RoleSynchronizer implements Synchronizer<RoleRemovedEvent> {
@Override
public void synchronize(RoleRemovedEvent event, KeycloakSessionFactory factory) {
ProviderFactory<AuthorizationProvider> providerFactory = factory.getProviderFactory(AuthorizationProvider.class);
AuthorizationProvider authorizationProvider = providerFactory.create(event.getKeycloakSession());
AdminPermissionsSchema.SCHEMA.removeResourceObject(authorizationProvider, event);
}
}

View File

@ -18,14 +18,11 @@
package org.keycloak.authorization.store.syncronization;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PermissionTicketStore;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
@ -34,7 +31,6 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.UserRemovedEvent;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -46,6 +42,8 @@ public class UserSynchronizer implements Synchronizer<UserRemovedEvent> {
ProviderFactory<AuthorizationProvider> providerFactory = factory.getProviderFactory(AuthorizationProvider.class);
AuthorizationProvider authorizationProvider = providerFactory.create(event.getKeycloakSession());
AdminPermissionsSchema.SCHEMA.removeResourceObject(authorizationProvider, event);
removeFromUserPermissionTickets(event, authorizationProvider);
removeUserResources(event, authorizationProvider);
}

View File

@ -1,5 +1,6 @@
package org.keycloak.testframework.realm;
import jakarta.ws.rs.NotFoundException;
import java.util.List;
import jakarta.ws.rs.core.Response;
@ -59,7 +60,9 @@ public class ClientSupplier implements Supplier<ManagedClient, InjectClient> {
@Override
public void close(InstanceContext<ManagedClient, InjectClient> instanceContext) {
if (instanceContext.getNote("managed", Boolean.class)) {
instanceContext.getValue().admin().remove();
try {
instanceContext.getValue().admin().remove();
} catch (NotFoundException ex) {}
}
}

View File

@ -1,5 +1,6 @@
package org.keycloak.testframework.realm;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.keycloak.admin.client.resource.UserResource;
@ -49,7 +50,9 @@ public class UserSupplier implements Supplier<ManagedUser, InjectUser> {
@Override
public void close(InstanceContext<ManagedUser, InjectUser> instanceContext) {
instanceContext.getValue().admin().remove();
try {
instanceContext.getValue().admin().remove();
} catch (NotFoundException ex) {}
}
}

View File

@ -92,10 +92,6 @@ public abstract class AbstractPermissionTest {
permission.setLogic(logic);
return this;
}
PermissionBuilder name(String name) {
permission.setName(name);
return this;
}
PermissionBuilder resourceType(String resourceType) {
permission.setResourceType(resourceType);
return this;
@ -114,35 +110,43 @@ public abstract class AbstractPermissionTest {
}
}
protected static UserPolicyRepresentation createUserPolicy(ManagedRealm realm, ManagedClient client, String name, String userId) {
return createUserPolicy(realm, client, name, userId, Logic.POSITIVE);
protected static UserPolicyRepresentation createUserPolicy(ManagedRealm realm, ManagedClient client, String name, String... userIds) {
return createUserPolicy(Logic.POSITIVE, realm, client, name, userIds);
}
protected static UserPolicyRepresentation createUserPolicy(ManagedRealm realm, ManagedClient client, String name, String userId, Logic logic) {
protected static UserPolicyRepresentation createUserPolicy(Logic logic, ManagedRealm realm, ManagedClient client, String name, String... userIds) {
UserPolicyRepresentation policy = new UserPolicyRepresentation();
policy.setName(name);
policy.addUser(userId);
for (String userId : userIds) {
policy.addUser(userId);
}
policy.setLogic(logic);
try (Response response = client.admin().authorization().policies().user().create(policy)) {
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
realm.cleanup().add(r -> {
String policyId = r.clients().get(client.getId()).authorization().policies().user().findByName(name).getId();
r.clients().get(client.getId()).authorization().policies().user().findById(policyId).remove();
UserPolicyRepresentation userPolicy = r.clients().get(client.getId()).authorization().policies().user().findByName(name);
if (userPolicy != null) {
r.clients().get(client.getId()).authorization().policies().user().findById(userPolicy.getId()).remove();
}
});
}
return policy;
}
protected static ClientPolicyRepresentation createClientPolicy(ManagedRealm realm, ManagedClient client, String name, String clientId) {
protected static ClientPolicyRepresentation createClientPolicy(ManagedRealm realm, ManagedClient client, String name, String... clientIds) {
ClientPolicyRepresentation policy = new ClientPolicyRepresentation();
policy.setName(name);
policy.addClient(clientId);
for (String clientId : clientIds) {
policy.addClient(clientId);
}
policy.setLogic(Logic.POSITIVE);
try (Response response = client.admin().authorization().policies().client().create(policy)) {
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
realm.cleanup().add(r -> {
String policyId = r.clients().get(client.getId()).authorization().policies().client().findByName(name).getId();
r.clients().get(client.getId()).authorization().policies().client().findById(policyId).remove();
ClientPolicyRepresentation clientPolicy = r.clients().get(client.getId()).authorization().policies().client().findByName(name);
if (clientPolicy != null) {
r.clients().get(client.getId()).authorization().policies().client().findById(clientPolicy.getId()).remove();
}
});
}
return policy;

View File

@ -0,0 +1,106 @@
package org.keycloak.tests.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.realm.ManagedClient;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class ClientResourceTypePermissionTest extends AbstractPermissionTest {
@InjectClient(ref = "testClient")
ManagedClient testClient;
@InjectClient(ref = "testClient2", lifecycle = LifeCycle.METHOD)
ManagedClient testClient2;
@BeforeEach
public void onBefore() {
UserPolicyRepresentation policy = new UserPolicyRepresentation();
policy.setName("User Policy");
client.admin().authorization().policies().user().create(policy).close();
}
@AfterEach
public void onAfter() {
ScopePermissionsResource permissions = getScopePermissionsResource(client);
for (ScopePermissionRepresentation permission : permissions.findAll(null, null, null, -1, -1)) {
permissions.findById(permission.getId()).remove();
}
}
@Test
public void testRemoveClient() {
//create client policies
createClientPolicy(realm, client, "Only testClient or testClient2 Client Policy", testClient.getId(), testClient2.getId());
createClientPolicy(realm, client, "Only testClient2 Client Policy", testClient2.getId());
//create client permissions
createClientPermission(testClient, testClient2);
createClientPermission(testClient2);
List<PolicyRepresentation> policies = getPolicies().policies(null, "Only", "client", null, null, null, null, null, null, null);
assertThat(policies, hasSize(2));
assertThat(policies.get(0).getConfig().get("clients"), containsString(testClient2.getId()));
assertThat(policies.get(1).getConfig().get("clients"), containsString(testClient2.getId()));
List<ScopePermissionRepresentation> permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(2));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(testClient2.getId()));
assertThat(getPolicies().policy(permissions.get(1).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(testClient2.getId()));
//remove client
realm.admin().clients().get(testClient2.getId()).remove();
//check the resource was removed from policies
ClientPolicyRepresentation clientPolicy = getPolicies().client().findByName("Only testClient or testClient2 Client Policy");
assertThat(clientPolicy, notNullValue());
assertThat(clientPolicy.getClients(), not(contains(testClient2.getId())));
ClientPolicyRepresentation clientPolicy1 = getPolicies().client().findByName("Only testClient2 Client Policy");
assertThat(clientPolicy1, notNullValue());
assertThat(clientPolicy1.getClients(), empty());
//there should be 1 permission left
permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(1));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), not(hasItem(testClient2.getId())));
}
private ScopePermissionRepresentation createClientPermission(ManagedClient... clients) {
ScopePermissionRepresentation permission = PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.CLIENTS.getType())
.resources(Arrays.stream(clients).map(ManagedClient::getClientId).collect(Collectors.toSet()))
.scopes(AdminPermissionsSchema.CLIENTS.getScopes())
.addPolicies(List.of("User Policy"))
.build();
createPermission(client, permission);
return permission;
}
}

View File

@ -65,7 +65,7 @@ public class GroupResourceTypeEvaluationTest extends AbstractPermissionTest {
Keycloak realmAdminClient;
private final String groupName = "top_group";
private final GroupRepresentation topGroup = new GroupRepresentation();;
private final GroupRepresentation topGroup = new GroupRepresentation();
@BeforeEach // cannot use @BeforeAll, realm is not initializaed yet
public void onBefore() {

View File

@ -0,0 +1,136 @@
package org.keycloak.tests.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import jakarta.ws.rs.core.Response;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation;
import org.keycloak.representations.idm.authorization.GroupPolicyRepresentation.GroupDefinition;
import org.keycloak.representations.idm.authorization.Logic;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.util.ApiUtil;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class GroupResourceTypePermissionTest extends AbstractPermissionTest {
@BeforeEach
public void onBefore() {
UserPolicyRepresentation policy = new UserPolicyRepresentation();
policy.setName("User Policy");
client.admin().authorization().policies().user().create(policy).close();
}
@AfterEach
public void onAfter() {
ScopePermissionsResource permissions = getScopePermissionsResource(client);
for (ScopePermissionRepresentation permission : permissions.findAll(null, null, null, -1, -1)) {
permissions.findById(permission.getId()).remove();
}
}
@Test
public void testRemoveGroup() {
//create groups
GroupRepresentation topGroup = new GroupRepresentation();
topGroup.setName("topGroup");
try (Response response = realm.admin().groups().add(topGroup)) {
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
topGroup.setId(ApiUtil.handleCreatedResponse(response));
realm.cleanup().add(r -> r.groups().group(topGroup.getId()).remove());
}
GroupRepresentation topGroup1 = new GroupRepresentation();
topGroup1.setName("topGroup1");
try (Response response = realm.admin().groups().add(topGroup1)) {
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
topGroup1.setId(ApiUtil.handleCreatedResponse(response));
}
//create group policies
createGroupPolicy("Only topGroup or topGroup1 Group Policy", topGroup.getId(), topGroup1.getId());
createGroupPolicy("Only topGroup1 Group Policy", topGroup1.getId());
//create group permissions
createGroupPermission(topGroup, topGroup1);
createGroupPermission(topGroup1);
List<PolicyRepresentation> policies = getPolicies().policies(null, "Only", "group", null, null, null, null, null, null, null);
assertThat(policies, hasSize(2));
assertThat(policies.get(0).getConfig().get("groups"), containsString(topGroup1.getId()));
assertThat(policies.get(1).getConfig().get("groups"), containsString(topGroup1.getId()));
List<ScopePermissionRepresentation> permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(2));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(topGroup1.getId()));
assertThat(getPolicies().policy(permissions.get(1).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(topGroup1.getId()));
//remove group
realm.admin().groups().group(topGroup1.getId()).remove();
//check the resource was removed from policies
GroupPolicyRepresentation groupPolicy = getPolicies().group().findByName("Only topGroup or topGroup1 Group Policy");
assertThat(groupPolicy, notNullValue());
Set<String> groupIds = groupPolicy.getGroups().stream().map(GroupDefinition::getId).collect(Collectors.toSet());
assertThat(groupIds, not(contains(topGroup1.getId())));
GroupPolicyRepresentation groupPolicy1 = getPolicies().group().findByName("Only topGroup1 Group Policy");
assertThat(groupPolicy1, notNullValue());
assertThat(groupPolicy1.getGroups().stream().map(GroupDefinition::getId).collect(Collectors.toSet()), empty());
//there should be 1 permission left
permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(1));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), not(hasItem(topGroup1.getId())));
}
private ScopePermissionRepresentation createGroupPermission(GroupRepresentation... groups) {
ScopePermissionRepresentation permission = PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.GROUPS.getType())
.resources(Arrays.stream(groups).map(GroupRepresentation::getId).collect(Collectors.toSet()))
.scopes(AdminPermissionsSchema.GROUPS.getScopes())
.addPolicies(List.of("User Policy"))
.build();
createPermission(client, permission);
return permission;
}
private GroupPolicyRepresentation createGroupPolicy(String name, String... groupIds) {
GroupPolicyRepresentation policy = new GroupPolicyRepresentation();
policy.setName(name);
policy.addGroup(groupIds);
policy.setLogic(Logic.POSITIVE);
try (Response response = client.admin().authorization().policies().group().create(policy)) {
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
realm.cleanup().add(r -> {
GroupPolicyRepresentation groupPolicy = client.admin().authorization().policies().group().findByName(name);
if (groupPolicy != null) {
client.admin().authorization().policies().group().findById(groupPolicy.getId()).remove();
}
});
}
return policy;
}
}

View File

@ -0,0 +1,144 @@
package org.keycloak.tests.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import jakarta.ws.rs.core.Response;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.authorization.Logic;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation;
import org.keycloak.representations.idm.authorization.RolePolicyRepresentation.RoleDefinition;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.ManagedClient;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class RoleResourceTypePermissionTest extends AbstractPermissionTest {
@InjectClient(ref = "testClient")
ManagedClient testClient;
@BeforeEach
public void onBefore() {
UserPolicyRepresentation policy = new UserPolicyRepresentation();
policy.setName("User Policy");
client.admin().authorization().policies().user().create(policy).close();
}
@AfterEach
public void onAfter() {
ScopePermissionsResource permissions = getScopePermissionsResource(client);
for (ScopePermissionRepresentation permission : permissions.findAll(null, null, null, -1, -1)) {
permissions.findById(permission.getId()).remove();
}
}
@Test
public void testRemoveRole() {
//create roles
RoleRepresentation realmRole = new RoleRepresentation();
realmRole.setName("realmRole");
try {
realm.admin().roles().create(realmRole);
realmRole.setId(realm.admin().roles().get(realmRole.getName()).toRepresentation().getId());
} finally {
realm.cleanup().add(r -> r.roles().deleteRole(realmRole.getName()));
}
RoleRepresentation clientRole = new RoleRepresentation();
clientRole.setName("clientRole");
clientRole.setClientRole(Boolean.TRUE);
clientRole.setContainerId(testClient.getId());
realm.admin().roles().create(clientRole);
clientRole.setId(realm.admin().roles().get(clientRole.getName()).toRepresentation().getId());
//create role policies
createRolePolicy("Only realmRole or clientRole Role Policy", realmRole.getId(), clientRole.getId());
createRolePolicy("Only clientRole Role Policy", clientRole.getId());
//create role permissions
createRolePermission(realmRole, clientRole);
createRolePermission(clientRole);
List<PolicyRepresentation> policies = getPolicies().policies(null, "Only", "role", null, null, null, null, null, null, null);
assertThat(policies, hasSize(2));
assertThat(policies.get(0).getConfig().get("roles"), containsString(clientRole.getId()));
assertThat(policies.get(1).getConfig().get("roles"), containsString(clientRole.getId()));
List<ScopePermissionRepresentation> permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(2));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(clientRole.getId()));
assertThat(getPolicies().policy(permissions.get(1).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(clientRole.getId()));
//remove role
realm.admin().roles().get(clientRole.getName()).remove();
//check the resource was removed from policies
RolePolicyRepresentation rolePolicy = getPolicies().role().findByName("Only realmRole or clientRole Role Policy");
assertThat(rolePolicy, notNullValue());
Set<String> roleIds = rolePolicy.getRoles().stream().map(RoleDefinition::getId).collect(Collectors.toSet());
assertThat(roleIds, not(contains(clientRole.getId())));
RolePolicyRepresentation rolePolicy1 = getPolicies().role().findByName("Only clientRole Role Policy");
assertThat(rolePolicy1, notNullValue());
assertThat(rolePolicy1.getRoles().stream().map(RoleDefinition::getId).collect(Collectors.toSet()), empty());
//there should be 1 permission left
permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(1));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), not(hasItem(clientRole.getId())));
}
private ScopePermissionRepresentation createRolePermission(RoleRepresentation... roles) {
ScopePermissionRepresentation permission = PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.ROLES.getType())
.resources(Arrays.stream(roles).map(RoleRepresentation::getId).collect(Collectors.toSet()))
.scopes(AdminPermissionsSchema.ROLES.getScopes())
.addPolicies(List.of("User Policy"))
.build();
createPermission(client, permission);
return permission;
}
private RolePolicyRepresentation createRolePolicy(String name, String... roleIds) {
RolePolicyRepresentation policy = new RolePolicyRepresentation();
policy.setName(name);
for (String roleId : roleIds) {
policy.addRole(roleId);
}
policy.setLogic(Logic.POSITIVE);
try (Response response = client.admin().authorization().policies().role().create(policy)) {
assertThat(response.getStatus(), equalTo(Response.Status.CREATED.getStatusCode()));
realm.cleanup().add(r -> {
RolePolicyRepresentation rolePolicy = client.admin().authorization().policies().role().findByName(name);
if (rolePolicy != null) {
client.admin().authorization().policies().role().findById(rolePolicy.getId()).remove();
}
});
}
return policy;
}
}

View File

@ -205,7 +205,7 @@ public class UserResourceTypeEvaluationTest extends AbstractPermissionTest {
UserPolicyRepresentation allowMyAdminPermission = createUserPolicy(realm, client,"Only My Admin User Policy", myadmin.getId());
createAllPermission(client, usersType, allowMyAdminPermission, Set.of(VIEW));
UserPolicyRepresentation denyMyAdminAccessingHisAccountPermission = createUserPolicy(realm, client,"Not My Admin User Policy", myadmin.getId(), Logic.NEGATIVE);
UserPolicyRepresentation denyMyAdminAccessingHisAccountPermission = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", myadmin.getId());
createPermission(client, myadmin.getId(), usersType, Set.of(VIEW), denyMyAdminAccessingHisAccountPermission);
List<UserRepresentation> search = realmAdminClient.realm(realm.getName()).users().search(null, -1, -1);
assertEquals(1, search.size());
@ -235,7 +235,7 @@ public class UserResourceTypeEvaluationTest extends AbstractPermissionTest {
@Test
public void testViewUserPermissionDenyByDefault() {
String myadminId = realm.admin().users().search("myadmin").get(0).getId();
UserPolicyRepresentation disallowMyAdmin = createUserPolicy(realm, client,"Not My Admin User Policy", myadminId, Logic.NEGATIVE);
UserPolicyRepresentation disallowMyAdmin = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", myadminId);
createAllPermission(client, usersType, disallowMyAdmin, Set.of(VIEW));
UserPolicyRepresentation allowAliceOnlyForMyAdmin = createUserPolicy(realm, client,"My Admin User Policy", myadminId);

View File

@ -18,10 +18,15 @@
package org.keycloak.tests.admin.authz.fgap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -39,17 +44,19 @@ import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ScopePermissionResource;
import org.keycloak.admin.client.resource.ScopePermissionsResource;
import org.keycloak.authorization.AdminPermissionsSchema;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testframework.annotations.InjectUser;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.realm.ManagedUser;
@KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class)
public class UserResourceTypePermissionTest extends AbstractPermissionTest {
@InjectUser(ref = "alice")
@InjectUser(ref = "alice", lifecycle = LifeCycle.METHOD)
ManagedUser userAlice;
@InjectUser(ref = "bob")
@ -227,6 +234,44 @@ public class UserResourceTypePermissionTest extends AbstractPermissionTest {
assertThat(permissionResources.get(0).getName(), is(AdminPermissionsSchema.USERS.getType()));
}
@Test
public void testRemoveUser() {
//create user policies
createUserPolicy(realm, client, "Only Alice or Bob User Policy", userAlice.getId(), userBob.getId());
createUserPolicy(realm, client, "Only Alice User Policy", userAlice.getId());
//create user permissions
createUserPermission(userAlice, userBob);
createUserPermission(userAlice);
List<PolicyRepresentation> policies = getPolicies().policies(null, "Only", "user", null, null, null, null, null, null, null);
assertThat(policies, hasSize(2));
assertThat(policies.get(0).getConfig().get("users"), containsString(userAlice.getId()));
assertThat(policies.get(1).getConfig().get("users"), containsString(userAlice.getId()));
List<ScopePermissionRepresentation> permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(2));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(userAlice.getId()));
assertThat(getPolicies().policy(permissions.get(1).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), hasItem(userAlice.getId()));
//remove user
realm.admin().users().get(userAlice.getId()).remove();
//check the resource was removed from policies
UserPolicyRepresentation userPolicy = getPolicies().user().findByName("Only Alice or Bob User Policy");
assertThat(userPolicy, notNullValue());
assertThat(userPolicy.getUsers(), not(contains(userAlice.getId())));
UserPolicyRepresentation userPolicy1 = getPolicies().user().findByName("Only Alice User Policy");
assertThat(userPolicy1, notNullValue());
assertThat(userPolicy1.getUsers(), empty());
//there should be 1 permission left
permissions = getScopePermissionsResource(client).findAll(null, null, null, null, null);
assertThat(permissions, hasSize(1));
assertThat(getPolicies().policy(permissions.get(0).getId()).resources().stream().map(ResourceRepresentation::getName).collect(Collectors.toList()), not(hasItem(userAlice.getId())));
}
private ScopePermissionRepresentation createUserPermission(ManagedUser... users) {
ScopePermissionRepresentation permission = PermissionBuilder.create()
.resourceType(AdminPermissionsSchema.USERS.getType())