Fix GroupLDAPStorageMapper so it doesn't attempt to update a group fetched in a different tx when synchronizing groups from LDAP
Closes #29784 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
parent
ae69b3b260
commit
c49b5749ef
2 changed files with 65 additions and 5 deletions
|
@ -194,13 +194,15 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
||||||
return syncResult;
|
return syncResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncExistingGroup(GroupModel kcExistingGroup, Map.Entry<String, LDAPObject> groupEntry,
|
private void syncExistingGroup(RealmModel realm, GroupModel kcExistingGroup, Map.Entry<String, LDAPObject> groupEntry,
|
||||||
SynchronizationResult syncResult, Set<String> visitedGroupIds, String groupName) {
|
SynchronizationResult syncResult, Set<String> visitedGroupIds, String groupName) {
|
||||||
try {
|
try {
|
||||||
// Update each existing group to be synced in its own inner transaction to prevent race condition when
|
// Update each existing group to be synced in its own inner transaction to prevent race condition when
|
||||||
// the groups intended to be updated was already deleted via other channel in the meantime
|
// the groups intended to be updated was already deleted via other channel in the meantime
|
||||||
KeycloakModelUtils.runJobInTransaction(ldapProvider.getSession().getKeycloakSessionFactory(), session -> {
|
KeycloakModelUtils.runJobInTransaction(ldapProvider.getSession().getKeycloakSessionFactory(), session -> {
|
||||||
updateAttributesOfKCGroup(kcExistingGroup, groupEntry.getValue());
|
RealmModel innerTransactionRealm = session.realms().getRealm(realm.getId());
|
||||||
|
GroupModel innerTransactionGroup = session.groups().getGroupById(innerTransactionRealm, kcExistingGroup.getId());
|
||||||
|
updateAttributesOfKCGroup(innerTransactionGroup, groupEntry.getValue());
|
||||||
syncResult.increaseUpdated();
|
syncResult.increaseUpdated();
|
||||||
visitedGroupIds.add(kcExistingGroup.getId());
|
visitedGroupIds.add(kcExistingGroup.getId());
|
||||||
});
|
});
|
||||||
|
@ -278,9 +280,9 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
||||||
GroupModel kcExistingGroup = transactionGroupPathGroups.get(groupName);
|
GroupModel kcExistingGroup = transactionGroupPathGroups.get(groupName);
|
||||||
|
|
||||||
if (kcExistingGroup != null) {
|
if (kcExistingGroup != null) {
|
||||||
syncExistingGroup(kcExistingGroup, groupEntry, syncResult, visitedGroupIds, groupName);
|
syncExistingGroup(currentRealm, kcExistingGroup, groupEntry, syncResult, visitedGroupIds, groupName);
|
||||||
} else {
|
} else {
|
||||||
syncNonExistingGroup(realm, groupEntry, syncResult, visitedGroupIds, groupName);
|
syncNonExistingGroup(currentRealm, groupEntry, syncResult, visitedGroupIds, groupName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||||
import org.keycloak.storage.ldap.LDAPUtils;
|
import org.keycloak.storage.ldap.LDAPUtils;
|
||||||
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
||||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||||
|
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
|
||||||
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
|
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||||
import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
||||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper;
|
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper;
|
||||||
|
@ -52,6 +53,7 @@ import jakarta.ws.rs.BadRequestException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName;
|
import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName;
|
||||||
|
|
||||||
|
@ -114,7 +116,6 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest {
|
||||||
testingClient.server().run(session -> {
|
testingClient.server().run(session -> {
|
||||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||||
RealmModel realm = ctx.getRealm();
|
RealmModel realm = ctx.getRealm();
|
||||||
String descriptionAttrName = LDAPTestUtils.getGroupDescriptionLDAPAttrName(ctx.getLdapProvider());
|
|
||||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(realm, ctx.getLdapModel(), "groupsMapper");
|
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(realm, ctx.getLdapModel(), "groupsMapper");
|
||||||
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
|
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
|
||||||
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
|
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
|
||||||
|
@ -500,4 +501,61 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test08_flatSynchWithBatchSizeLessThanNumberOfGroups() {
|
||||||
|
// update the LDAP config to use pagination and a batch size that is less than the number of LDAP groups.
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||||
|
RealmModel realm = ctx.getRealm();
|
||||||
|
ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(realm);
|
||||||
|
LDAPTestUtils.updateConfigOptions(ldapModel, LDAPConstants.PAGINATION, "true", LDAPConstants.BATCH_SIZE_FOR_SYNC, "1");
|
||||||
|
realm.updateComponent(ldapModel);
|
||||||
|
|
||||||
|
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(ctx.getRealm(), ldapModel, "groupsMapper");
|
||||||
|
LDAPTestUtils.updateConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
|
||||||
|
realm.updateComponent(mapperModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||||
|
RealmModel realm = ctx.getRealm();
|
||||||
|
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(realm, ctx.getLdapModel(), "groupsMapper");
|
||||||
|
|
||||||
|
// check right config is in place.
|
||||||
|
Assert.assertEquals(ctx.getLdapModel().getConfig().getFirst(LDAPConstants.PAGINATION), "true");
|
||||||
|
Assert.assertEquals(ctx.getLdapModel().getConfig().getFirst(LDAPConstants.BATCH_SIZE_FOR_SYNC), "1");
|
||||||
|
Assert.assertEquals(mapperModel.getConfig().getFirst(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE), "false");
|
||||||
|
|
||||||
|
// synch groups a first time - imports all new groups into keycloak.
|
||||||
|
LDAPStorageMapper groupMapper = new GroupLDAPStorageMapperFactory().create(session, mapperModel);
|
||||||
|
SynchronizationResult syncResult = groupMapper.syncDataFromFederationProviderToKeycloak(realm);
|
||||||
|
LDAPTestAsserts.assertSyncEquals(syncResult, 3, 0, 0, 0);
|
||||||
|
|
||||||
|
// check all groups were imported as top level groups with no subgroups.
|
||||||
|
Stream.of("/group1", "/group11", "/group12").forEach(path -> {
|
||||||
|
GroupModel kcGroup = KeycloakModelUtils.findGroupByPath(session, realm, path);
|
||||||
|
Assert.assertNotNull(kcGroup);
|
||||||
|
Assert.assertEquals(0, kcGroup.getSubGroupsStream().count());
|
||||||
|
});
|
||||||
|
|
||||||
|
// re-synch groups, updating previously imported groups.
|
||||||
|
syncResult = groupMapper.syncDataFromFederationProviderToKeycloak(realm);
|
||||||
|
LDAPTestAsserts.assertSyncEquals(syncResult, 0, 3, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// restore pagination, batch size and preserve inheritance configs.
|
||||||
|
testingClient.server().run(session -> {
|
||||||
|
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||||
|
RealmModel realm = ctx.getRealm();
|
||||||
|
ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(realm);
|
||||||
|
ldapModel.getConfig().putSingle(LDAPConstants.PAGINATION, "false");
|
||||||
|
ldapModel.getConfig().remove(LDAPConstants.BATCH_SIZE_FOR_SYNC);
|
||||||
|
realm.updateComponent(ldapModel);
|
||||||
|
|
||||||
|
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(ctx.getRealm(), ldapModel, "groupsMapper");
|
||||||
|
LDAPTestUtils.updateConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
|
||||||
|
realm.updateComponent(mapperModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue