Deleting a User or User Group might cause that all users suddenly get the permissions of the deleted user.

Closes #24651
Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2023-11-16 09:15:56 -08:00 committed by Pedro Igor
parent badf3f461d
commit 58d167fe59
11 changed files with 96 additions and 199 deletions

View file

@ -166,7 +166,7 @@ public class ClientPolicyProviderFactory implements PolicyProviderFactory<Client
private void updateClients(Policy policy, Set<String> clients, AuthorizationProvider authorization) { private void updateClients(Policy policy, Set<String> clients, AuthorizationProvider authorization) {
RealmModel realm = authorization.getRealm(); RealmModel realm = authorization.getRealm();
if (clients == null || clients.isEmpty()) { if (clients == null) {
throw new RuntimeException("No client provided."); throw new RuntimeException("No client provided.");
} }

View file

@ -154,7 +154,7 @@ public class GroupPolicyProviderFactory implements PolicyProviderFactory<GroupPo
} }
private void updatePolicy(Policy policy, String groupsClaim, Set<GroupPolicyRepresentation.GroupDefinition> groups, AuthorizationProvider authorization) { private void updatePolicy(Policy policy, String groupsClaim, Set<GroupPolicyRepresentation.GroupDefinition> groups, AuthorizationProvider authorization) {
if (groups == null || groups.isEmpty()) { if (groups == null) {
throw new RuntimeException("You must provide at least one group"); throw new RuntimeException("You must provide at least one group");
} }

View file

@ -124,3 +124,7 @@ When enabling metrics for {project_name}'s embedded caches, the metrics now use
For more details, check the For more details, check the
link:{upgradingguide_link}[{upgradingguide_name}]. link:{upgradingguide_link}[{upgradingguide_name}].
= Authorization Policy
In previous versions of Keycloak when the last member of a User, Group or Client policy was deleted then that policy would also be deleted. Unfortunately this could lead to an escalation of privileges if the policy was used in an aggregate policy. To avoid privilege escalation the effect policies are no longer deleted and an administrator will need to update those policies.

View file

@ -72,12 +72,7 @@ public class ClientApplicationSynchronizer implements Synchronizer<ClientRemoved
clients.remove(event.getClient().getId()); clients.remove(event.getClient().getId());
if (clients.isEmpty()) {
policyFactory.onRemove(policy, authorizationProvider);
authorizationProvider.getStoreFactory().getPolicyStore().delete(realm, policy.getId());
} else {
policyFactory.onUpdate(policy, representation, authorizationProvider); policyFactory.onUpdate(policy, representation, authorizationProvider);
} }
} }
} }
}

View file

@ -62,12 +62,7 @@ public class GroupSynchronizer implements Synchronizer<GroupModel.GroupRemovedEv
groups.removeIf(groupDefinition -> groupDefinition.getId().equals(group.getId())); groups.removeIf(groupDefinition -> groupDefinition.getId().equals(group.getId()));
if (groups.isEmpty()) {
policyFactory.onRemove(policy, authorizationProvider);
policyStore.delete(realm, policy.getId());
} else {
policyFactory.onUpdate(policy, representation, authorizationProvider); policyFactory.onUpdate(policy, representation, authorizationProvider);
} }
} }
} }
}

View file

@ -72,14 +72,9 @@ public class UserSynchronizer implements Synchronizer<UserRemovedEvent> {
users.remove(userModel.getId()); users.remove(userModel.getId());
if (users.isEmpty()) {
policyFactory.onRemove(policy, authorizationProvider);
policyStore.delete(realm, policy.getId());
} else {
policyFactory.onUpdate(policy, representation, authorizationProvider); policyFactory.onUpdate(policy, representation, authorizationProvider);
} }
} }
}
private void removeUserResources(UserRemovedEvent event, AuthorizationProvider authorizationProvider) { private void removeUserResources(UserRemovedEvent event, AuthorizationProvider authorizationProvider) {
StoreFactory storeFactory = authorizationProvider.getStoreFactory(); StoreFactory storeFactory = authorizationProvider.getStoreFactory();

View file

@ -488,44 +488,6 @@ public class UserManagedPermissionServiceTest extends AbstractResourceServerTest
} }
@Test
public void testRemoveUserPolicyWhenUserDeleted() {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Resource A");
resource.setOwnerManagedAccess(true);
resource.setOwner("marta");
resource.addScope("Scope A", "Scope B", "Scope C");
resource = getAuthzClient().protection().resource().create(resource);
UmaPermissionRepresentation permission = new UmaPermissionRepresentation();
permission.setName("Custom User-Managed Permission");
permission.addUser("kolo");
ProtectionResource protection = getAuthzClient().protection("marta", "password");
protection.policy(resource.getId()).create(permission);
AuthorizationResource authorization = getAuthzClient().authorization("kolo", "password");
AuthorizationRequest request = new AuthorizationRequest();
request.addPermission(resource.getId(), "Scope A");
AuthorizationResponse authzResponse = authorization.authorize(request);
assertNotNull(authzResponse);
UsersResource users = realmsResouce().realm(REALM_NAME).users();
UserRepresentation kolo = users.search("kolo").get(0);
users.delete(kolo.getId());
assertTrue(protection.policy(resource.getId()).find(null, null, null, null).isEmpty());
}
@Test @Test
public void testRemovePolicyWhenOwnerDeleted() { public void testRemovePolicyWhenOwnerDeleted() {
ResourceRepresentation resource = new ResourceRepresentation(); ResourceRepresentation resource = new ResourceRepresentation();
@ -1021,116 +983,6 @@ public class UserManagedPermissionServiceTest extends AbstractResourceServerTest
assertTrue(policies.isEmpty()); assertTrue(policies.isEmpty());
} }
@Test
public void testRemovePoliciesOnGroupDelete() {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Resource A");
resource.setOwnerManagedAccess(true);
resource.setOwner("marta");
resource.addScope("Scope A", "Scope B", "Scope C");
resource = getAuthzClient().protection().resource().create(resource);
UmaPermissionRepresentation newPermission = new UmaPermissionRepresentation();
newPermission.setName("Custom User-Managed Permission");
newPermission.addGroup("/group_remove");
ProtectionResource protection = getAuthzClient().protection("marta", "password");
protection.policy(resource.getId()).create(newPermission);
getTestingClient().server().run((RunOnServer) UserManagedPermissionServiceTest::testRemovePoliciesOnGroupDelete);
}
private static void testRemovePoliciesOnGroupDelete(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName("authz-test");
ClientModel client = realm.getClientByClientId("resource-server-test");
AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
UserModel user = session.users().getUserByUsername(realm, "marta");
ResourceServer resourceServer = provider.getStoreFactory().getResourceServerStore().findByClient(client);
Map<Policy.FilterOption, String[]> filters = new HashMap<>();
filters.put(Policy.FilterOption.TYPE, new String[] {"uma"});
filters.put(OWNER, new String[] {user.getId()});
List<Policy> policies = provider.getStoreFactory().getPolicyStore()
.find(realm, resourceServer, filters, null, null);
assertEquals(1, policies.size());
Policy policy = policies.get(0);
assertFalse(policy.getResources().isEmpty());
Resource resource = policy.getResources().iterator().next();
assertEquals("Resource A", resource.getName());
realm.removeGroup(session.groups().searchForGroupByNameStream(realm, "group_remove", false, null, null).findAny().get());
filters = new HashMap<>();
filters.put(OWNER, new String[] {user.getId()});
policies = provider.getStoreFactory().getPolicyStore()
.find(realm, resourceServer, filters, null, null);
assertTrue(policies.isEmpty());
}
@Test
public void testRemovePoliciesOnClientDelete() {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName("Resource A");
resource.setOwnerManagedAccess(true);
resource.setOwner("marta");
resource.addScope("Scope A", "Scope B", "Scope C");
resource = getAuthzClient().protection().resource().create(resource);
UmaPermissionRepresentation newPermission = new UmaPermissionRepresentation();
newPermission.setName("Custom User-Managed Permission");
newPermission.addClient("client-remove");
ProtectionResource protection = getAuthzClient().protection("marta", "password");
protection.policy(resource.getId()).create(newPermission);
getTestingClient().server().run((RunOnServer) UserManagedPermissionServiceTest::testRemovePoliciesOnClientDelete);
}
private static void testRemovePoliciesOnClientDelete(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName("authz-test");
ClientModel client = realm.getClientByClientId("resource-server-test");
AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
UserModel user = session.users().getUserByUsername(realm, "marta");
ResourceServer resourceServer = provider.getStoreFactory().getResourceServerStore().findByClient(client);
Map<Policy.FilterOption, String[]> filters = new HashMap<>();
filters.put(Policy.FilterOption.TYPE, new String[] {"uma"});
filters.put(OWNER, new String[] {user.getId()});
List<Policy> policies = provider.getStoreFactory().getPolicyStore()
.find(realm, resourceServer, filters, null, null);
assertEquals(1, policies.size());
Policy policy = policies.get(0);
assertFalse(policy.getResources().isEmpty());
Resource resource = policy.getResources().iterator().next();
assertEquals("Resource A", resource.getName());
realm.removeClient(realm.getClientByClientId("client-remove").getId());
filters = new HashMap<>();
filters.put(OWNER, new String[] {user.getId()});
policies = provider.getStoreFactory().getPolicyStore()
.find(realm, resourceServer, filters, null, null);
assertTrue(policies.isEmpty());
}
private List<PolicyRepresentation> getAssociatedPolicies(UmaPermissionRepresentation permission) { private List<PolicyRepresentation> getAssociatedPolicies(UmaPermissionRepresentation permission) {
return getClient(getRealm()).authorization().policies().policy(permission.getId()).associatedPolicies(); return getClient(getRealm()).authorization().policies().policy(permission.getId()).associatedPolicies();
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.testsuite.authz.admin; package org.keycloak.testsuite.authz.admin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.Collections; import java.util.Collections;
@ -27,15 +28,29 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.AggregatePoliciesResource; import org.keycloak.admin.client.resource.AggregatePoliciesResource;
import org.keycloak.admin.client.resource.AggregatePolicyResource; import org.keycloak.admin.client.resource.AggregatePolicyResource;
import org.keycloak.admin.client.resource.AuthorizationResource; import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.UserPoliciesResource;
import org.keycloak.admin.client.resource.UserPolicyResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation; import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.Logic;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public class AggregatePolicyManagementTest extends AbstractPolicyManagementTest { public class AggregatePolicyManagementTest extends AbstractPolicyManagementTest {
@Override
protected RealmBuilder createTestRealm() {
return super.createTestRealm()
.user(UserBuilder.create().username("AggregatePolicyManagementTestUser"));
}
@Test @Test
public void testCreate() { public void testCreate() {
AuthorizationResource authorization = getClient().authorization(); AuthorizationResource authorization = getClient().authorization();
@ -103,6 +118,47 @@ public class AggregatePolicyManagementTest extends AbstractPolicyManagementTest
} }
} }
//Issue #24651
@Test
public void testDeleteUser() {
AuthorizationResource authorization = getClient().authorization();
UsersResource users = getRealm().users();
UserRepresentation user = users.search("AggregatePolicyManagementTestUser").get(0);
UserPolicyRepresentation userPolicyRepresentation = new UserPolicyRepresentation();
userPolicyRepresentation.setName("AggregatePolicyManagementTestUser Only");
userPolicyRepresentation.setDescription("description");
userPolicyRepresentation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
userPolicyRepresentation.setLogic(Logic.NEGATIVE);
userPolicyRepresentation.addUser(user.getId());
authorization.policies().user().create(userPolicyRepresentation);
TimePolicyRepresentation timePolicyRepresentation = new TimePolicyRepresentation();
timePolicyRepresentation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
timePolicyRepresentation.setLogic(Logic.NEGATIVE);
timePolicyRepresentation.setName("Dayshift");
timePolicyRepresentation.setHour("8");
timePolicyRepresentation.setHourEnd("17");
authorization.policies().time().create(timePolicyRepresentation);
AggregatePolicyRepresentation representation = new AggregatePolicyRepresentation();
representation.setName("AggregatePolicyManagementTestUser Only during dayshift");
representation.setDescription("description");
representation.setDecisionStrategy(DecisionStrategy.CONSENSUS);
representation.setLogic(Logic.NEGATIVE);
representation.addPolicy("AggregatePolicyManagementTestUser Only", "Dayshift");
assertCreated(authorization, representation);
users.get(user.getId()).remove();
UserPolicyRepresentation actualUserPolicy = authorization.policies().user().findByName(userPolicyRepresentation.getName());
assertEquals(0, actualUserPolicy.getUsers().size());
AggregatePolicyResource actual = authorization.policies().aggregate().findById(representation.getId());
assertRepresentation(representation, actual);
}
private void assertCreated(AuthorizationResource authorization, AggregatePolicyRepresentation representation) { private void assertCreated(AuthorizationResource authorization, AggregatePolicyRepresentation representation) {
AggregatePoliciesResource permissions = authorization.policies().aggregate(); AggregatePoliciesResource permissions = authorization.policies().aggregate();
try (Response response = permissions.create(representation)) { try (Response response = permissions.create(representation)) {
@ -112,8 +168,29 @@ public class AggregatePolicyManagementTest extends AbstractPolicyManagementTest
} }
} }
private void assertCreated(AuthorizationResource authorization, UserPolicyRepresentation representation) {
UserPoliciesResource permissions = authorization.policies().user();
try (Response response = permissions.create(representation)) {
UserPolicyRepresentation created = response.readEntity(UserPolicyRepresentation.class);
UserPolicyResource permission = permissions.findById(created.getId());
assertRepresentation(representation, permission);
}
}
private void assertRepresentation(AggregatePolicyRepresentation representation, AggregatePolicyResource policy) { private void assertRepresentation(AggregatePolicyRepresentation representation, AggregatePolicyResource policy) {
AggregatePolicyRepresentation actual = policy.toRepresentation(); AggregatePolicyRepresentation actual = policy.toRepresentation();
assertRepresentation(representation, actual, () -> policy.resources(), () -> Collections.emptyList(), () -> policy.associatedPolicies()); assertRepresentation(representation, actual, () -> policy.resources(), () -> Collections.emptyList(), () -> policy.associatedPolicies());
} }
private void assertRepresentation(UserPolicyRepresentation representation, UserPolicyResource permission) {
UserPolicyRepresentation actual = permission.toRepresentation();
assertRepresentation(representation, actual, () -> permission.resources(), () -> Collections.emptyList(), () -> permission.associatedPolicies());
assertEquals(representation.getUsers().size(), actual.getUsers().size());
assertEquals(0, actual.getUsers().stream().filter(userId -> !representation.getUsers().stream()
.filter(userName -> getRealm().users().get(userId).toRepresentation().getUsername().equalsIgnoreCase(userName))
.findFirst().isPresent())
.count());
}
} }

View file

@ -169,12 +169,10 @@ public class ClientPolicyManagementTest extends AbstractPolicyManagementTest {
client = clients.findByClientId("Client F").get(0); client = clients.findByClientId("Client F").get(0);
clients.get(client.getId()).remove(); clients.get(client.getId()).remove();
try { representation = authorization.policies().client().findById(representation.getId()).toRepresentation();
authorization.policies().client().findById(representation.getId()).toRepresentation();
fail("Client policy should be removed"); Assert.assertEquals(0, representation.getClients().size());
} catch (NotFoundException nfe) { Assert.assertFalse(representation.getClients().contains(client.getId()));
// ignore
}
} }
@Test @Test

View file

@ -231,25 +231,8 @@ public class GroupPolicyManagementTest extends AbstractPolicyManagementTest {
groups.group(group.getId()).remove(); groups.group(group.getId()).remove();
try { GroupPolicyRepresentation actual = getClient().authorization().policies().group().findByName(representation.getName());
getClient().authorization().policies().group().findByName(representation.getName()); assertEquals(0, actual.getGroups().size());
} catch (NotFoundException e) {
}
representation.getGroups().clear();
representation.addGroupPath("/Group H/Group I/Group K");
representation.addGroupPath("/Group F");
assertCreated(authorization, representation);
group = groups.groups("Group K", null, null).get(0);
groups.group(group.getId()).remove();
GroupPolicyRepresentation policy = getClient().authorization().policies().group().findByName(representation.getName());
assertNotNull(policy);
assertEquals(1, policy.getGroups().size());
} }
private void assertCreated(AuthorizationResource authorization, GroupPolicyRepresentation representation) { private void assertCreated(AuthorizationResource authorization, GroupPolicyRepresentation representation) {

View file

@ -171,12 +171,10 @@ public class UserPolicyManagementTest extends AbstractPolicyManagementTest {
user = users.search("User F").get(0); user = users.search("User F").get(0);
users.get(user.getId()).remove(); users.get(user.getId()).remove();
try { representation = authorization.policies().user().findById(representation.getId()).toRepresentation();
authorization.policies().user().findById(representation.getId()).toRepresentation();
fail("User policy should be removed"); Assert.assertEquals(0, representation.getUsers().size());
} catch (NotFoundException nfe) { Assert.assertFalse(representation.getUsers().contains(user.getId()));
// ignore
}
} }
@Test @Test