KEYCLOAK-13369 Not possible to move groups in admin console

This commit is contained in:
mposolda 2020-03-20 19:02:50 +01:00 committed by Marek Posolda
parent 61fd66e107
commit 3e82473a90
6 changed files with 86 additions and 3 deletions

View file

@ -997,7 +997,8 @@ public class RealmCacheSession implements CacheRealmProvider {
listInvalidations.add(realm.getId()); listInvalidations.add(realm.getId());
invalidateGroup(group.getId(), realm.getId(), true); invalidateGroup(group.getId(), realm.getId(), true);
if (toParent != null) invalidateGroup(toParent.getId(), realm.getId(), false); // Queries already invalidated if (toParent != null) invalidateGroup(toParent.getId(), realm.getId(), false); // Queries already invalidated
invalidationEvents.add(GroupAddedEvent.create(group.getId(), realm.getId())); String parentId = toParent == null ? null : toParent.getId();
invalidationEvents.add(GroupAddedEvent.create(group.getId(), parentId, realm.getId()));
return group; return group;
} }

View file

@ -36,10 +36,12 @@ public class GroupAddedEvent extends InvalidationEvent implements RealmCacheInva
private String groupId; private String groupId;
private String realmId; private String realmId;
private String parentId;
public static GroupAddedEvent create(String groupId, String realmId) { public static GroupAddedEvent create(String groupId, String parentId, String realmId) {
GroupAddedEvent event = new GroupAddedEvent(); GroupAddedEvent event = new GroupAddedEvent();
event.realmId = realmId; event.realmId = realmId;
event.parentId = parentId;
event.groupId = groupId; event.groupId = groupId;
return event; return event;
} }
@ -57,18 +59,23 @@ public class GroupAddedEvent extends InvalidationEvent implements RealmCacheInva
@Override @Override
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) { public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
realmCache.groupQueriesInvalidations(realmId, invalidations); realmCache.groupQueriesInvalidations(realmId, invalidations);
if (parentId != null) {
invalidations.add(parentId);
}
} }
public static class ExternalizerImpl implements Externalizer<GroupAddedEvent> { public static class ExternalizerImpl implements Externalizer<GroupAddedEvent> {
private static final int VERSION_1 = 1; private static final int VERSION_1 = 1;
private static final int VERSION_2 = 2;
@Override @Override
public void writeObject(ObjectOutput output, GroupAddedEvent obj) throws IOException { public void writeObject(ObjectOutput output, GroupAddedEvent obj) throws IOException {
output.writeByte(VERSION_1); output.writeByte(VERSION_2);
MarshallUtil.marshallString(obj.groupId, output); MarshallUtil.marshallString(obj.groupId, output);
MarshallUtil.marshallString(obj.realmId, output); MarshallUtil.marshallString(obj.realmId, output);
MarshallUtil.marshallString(obj.parentId, output);
} }
@Override @Override
@ -76,6 +83,8 @@ public class GroupAddedEvent extends InvalidationEvent implements RealmCacheInva
switch (input.readByte()) { switch (input.readByte()) {
case VERSION_1: case VERSION_1:
return readObjectVersion1(input); return readObjectVersion1(input);
case VERSION_2:
return readObjectVersion2(input);
default: default:
throw new IOException("Unknown version"); throw new IOException("Unknown version");
} }
@ -88,5 +97,14 @@ public class GroupAddedEvent extends InvalidationEvent implements RealmCacheInva
return res; return res;
} }
public GroupAddedEvent readObjectVersion2(ObjectInput input) throws IOException, ClassNotFoundException {
GroupAddedEvent res = new GroupAddedEvent();
res.groupId = MarshallUtil.unmarshallString(input);
res.realmId = MarshallUtil.unmarshallString(input);
res.parentId = MarshallUtil.unmarshallString(input);
return res;
}
} }
} }

View file

@ -158,6 +158,7 @@ public class GroupResource {
if (child == null) { if (child == null) {
throw new NotFoundException("Could not find child by id"); throw new NotFoundException("Could not find child by id");
} }
realm.moveGroup(child, group);
adminEvent.operation(OperationType.UPDATE); adminEvent.operation(OperationType.UPDATE);
} else { } else {
child = realm.createGroup(rep.getName(), group); child = realm.createGroup(rep.getName(), group);

View file

@ -150,6 +150,7 @@ public class GroupsResource {
if (child == null) { if (child == null) {
throw new NotFoundException("Could not find child by id"); throw new NotFoundException("Could not find child by id");
} }
realm.moveGroup(child, null);
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()); adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri());
} else { } else {
child = realm.createGroup(rep.getName()); child = realm.createGroup(rep.getName());

View file

@ -485,6 +485,45 @@ public class GroupTest extends AbstractGroupTest {
assertThat(group.getAttributes(), hasEntry(is("attr3"), contains("attrval2"))); assertThat(group.getAttributes(), hasEntry(is("attr3"), contains("attrval2")));
} }
@Test
public void moveGroups() {
RealmResource realm = adminClient.realms().realm("test");
// Create 2 top level groups "mygroup1" and "mygroup2"
GroupRepresentation group = GroupBuilder.create()
.name("mygroup1")
.build();
GroupRepresentation group1 = createGroup(realm, group);
group = GroupBuilder.create()
.name("mygroup2")
.build();
GroupRepresentation group2 = createGroup(realm, group);
// Move "mygroup2" as child of "mygroup1" . Assert it was moved
Response response = realm.groups().group(group1.getId()).subGroup(group2);
Assert.assertEquals(204, response.getStatus());
response.close();
// Assert "mygroup2" was moved
group1 = realm.groups().group(group1.getId()).toRepresentation();
group2 = realm.groups().group(group2.getId()).toRepresentation();
assertNames(group1.getSubGroups(), "mygroup2");
Assert.assertEquals("/mygroup1/mygroup2", group2.getPath());
// Move "mygroup2" back under parent
response = realm.groups().add(group2);
Assert.assertEquals(204, response.getStatus());
response.close();
// Assert "mygroup2" was moved
group1 = realm.groups().group(group1.getId()).toRepresentation();
group2 = realm.groups().group(group2.getId()).toRepresentation();
assertTrue(group1.getSubGroups().isEmpty());
Assert.assertEquals("/mygroup2", group2.getPath());
}
@Test @Test
public void groupMembership() { public void groupMembership() {
RealmResource realm = adminClient.realms().realm("test"); RealmResource realm = adminClient.realms().realm("test");

View file

@ -7,6 +7,7 @@ import org.keycloak.admin.client.resource.GroupsResource;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.ContainerInfo; import org.keycloak.testsuite.arquillian.ContainerInfo;
import org.keycloak.testsuite.util.GroupBuilder;
import javax.ws.rs.NotFoundException; import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -17,6 +18,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.Assert.assertNames;
/** /**
* *
@ -131,6 +133,27 @@ public class GroupInvalidationClusterTest extends AbstractInvalidationClusterTes
assertEquals(parentGroup.getPath() + "/" + group.getName(), group.getPath()); assertEquals(parentGroup.getPath() + "/" + group.getName(), group.getPath());
verifyEntityUpdateDuringFailover(group, backendFailover); verifyEntityUpdateDuringFailover(group, backendFailover);
parentGroup = readEntityOnCurrentFailNode(parentGroup);
// Add new child
GroupRepresentation childGroup2 = GroupBuilder.create()
.name("childGroup2")
.build();
r = entityResourceOnCurrentFailNode(parentGroup).subGroup(childGroup2);
String childGroup2Id = ApiUtil.getCreatedId(r);
childGroup2.setId(childGroup2Id);
parentGroup = readEntityOnCurrentFailNode(parentGroup);
verifyEntityUpdateDuringFailover(parentGroup, backendFailover);
// Verify same child groups on both nodes
GroupRepresentation parentGroupOnOtherNode = readEntityOnCurrentFailNode(parentGroup);
assertNames(parentGroup.getSubGroups(), group.getName(), "childGroup2");
assertNames(parentGroupOnOtherNode.getSubGroups(), group.getName(), "childGroup2");
// Remove childGroup2
deleteEntityOnCurrentFailNode(childGroup2);
return group; return group;
} }