[KEYCLOAK-8253] When syncing flat (all groups being the top-level ones) structure
of LDAP groups from federation provider to Keycloak, perform the search if the currently processed group already exists in Keycloak in log(N) time Signed-off-by: Jan Lieskovsky <jlieskov@redhat.com>
This commit is contained in:
parent
cfb225b499
commit
7ab854fecf
2 changed files with 60 additions and 11 deletions
|
@ -45,6 +45,8 @@ import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
||||||
import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
||||||
import org.keycloak.storage.user.SynchronizationResult;
|
import org.keycloak.storage.user.SynchronizationResult;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -213,21 +215,68 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
||||||
// due to the realm cache being bloated with huge amount of (temporary) realm entities
|
// due to the realm cache being bloated with huge amount of (temporary) realm entities
|
||||||
RealmModel currentRealm = session.realms().getRealm(realm.getId());
|
RealmModel currentRealm = session.realms().getRealm(realm.getId());
|
||||||
|
|
||||||
|
// List of top-level groups known to the whole transaction
|
||||||
|
ArrayList<GroupModel> transactionTopLevelGroups = new ArrayList<GroupModel>(currentRealm.getTopLevelGroups());
|
||||||
|
String[] transactionBinarySearchTopLevelGroupsArray = transactionTopLevelGroups.parallelStream().map(g -> g.getName()).toArray(String[]::new);
|
||||||
|
|
||||||
for (Map.Entry<String, LDAPObject> groupEntry : groupsInTransaction.entrySet()) {
|
for (Map.Entry<String, LDAPObject> groupEntry : groupsInTransaction.entrySet()) {
|
||||||
|
|
||||||
String groupName = groupEntry.getKey();
|
String groupName = groupEntry.getKey();
|
||||||
GroupModel kcExistingGroup = KeycloakModelUtils.findGroupByPath(currentRealm, "/" + groupName);
|
|
||||||
|
// Binary search the list of top-level groups known to the outer transaction for presence of the currently processed group
|
||||||
|
int transactionBinarySearchResult = Arrays.binarySearch(transactionBinarySearchTopLevelGroupsArray, groupName);
|
||||||
|
GroupModel kcExistingGroup = (transactionBinarySearchResult > 0) ? transactionTopLevelGroups.get(transactionBinarySearchResult) : null;
|
||||||
|
|
||||||
if (kcExistingGroup != null) {
|
if (kcExistingGroup != null) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 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
|
||||||
|
KeycloakModelUtils.runJobInTransaction(ldapProvider.getSession().getKeycloakSessionFactory(), new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
|
||||||
updateAttributesOfKCGroup(kcExistingGroup, groupEntry.getValue());
|
updateAttributesOfKCGroup(kcExistingGroup, groupEntry.getValue());
|
||||||
syncResult.increaseUpdated();
|
syncResult.increaseUpdated();
|
||||||
visitedGroupIds.add(kcExistingGroup.getId());
|
visitedGroupIds.add(kcExistingGroup.getId());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (ModelException me) {
|
||||||
|
logger.error(String.format("Failed to update attributes of LDAP group %s: ", groupName), me);
|
||||||
|
syncResult.increaseFailed();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
GroupModel kcGroup = currentRealm.createGroup(groupName);
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Create each non-existing group to be synced in its own inner transaction to prevent race condition when
|
||||||
|
// the roup intended to be created was already created via other channel in the meantime
|
||||||
|
KeycloakModelUtils.runJobInTransaction(ldapProvider.getSession().getKeycloakSessionFactory(), new KeycloakSessionTask() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(KeycloakSession session) {
|
||||||
|
|
||||||
|
RealmModel innerTransactionRealm = session.realms().getRealm(realm.getId());
|
||||||
|
GroupModel kcGroup = innerTransactionRealm.createGroup(groupName);
|
||||||
updateAttributesOfKCGroup(kcGroup, groupEntry.getValue());
|
updateAttributesOfKCGroup(kcGroup, groupEntry.getValue());
|
||||||
currentRealm.moveGroup(kcGroup, null);
|
innerTransactionRealm.moveGroup(kcGroup, null);
|
||||||
syncResult.increaseAdded();
|
syncResult.increaseAdded();
|
||||||
visitedGroupIds.add(kcGroup.getId());
|
visitedGroupIds.add(kcGroup.getId());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (ModelException me) {
|
||||||
|
logger.error(String.format("Failed to sync group %s from LDAP: ", groupName), me);
|
||||||
|
syncResult.increaseFailed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,9 +422,9 @@ public class LDAPGroupMapperSyncTest extends AbstractLDAPTest {
|
||||||
LDAPTestUtils.createLDAPGroup(session,
|
LDAPTestUtils.createLDAPGroup(session,
|
||||||
appRealm,
|
appRealm,
|
||||||
ctx.getLdapModel(),
|
ctx.getLdapModel(),
|
||||||
String.format("group-%s", i),
|
String.format("group-%s", j),
|
||||||
descriptionAttrName,
|
descriptionAttrName,
|
||||||
String.format("Testing group-%s, created at: %s", i, new Date().toString())
|
String.format("Testing group-%s, created at: %s", j, new Date().toString())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
logger.debugf("Done creating %s LDAP groups!", groupsPerIteration);
|
logger.debugf("Done creating %s LDAP groups!", groupsPerIteration);
|
||||||
|
|
Loading…
Reference in a new issue