/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.storage.ldap.mappers.membership.group; import org.jboss.logging.Logger; import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RoleUtils; import org.keycloak.models.utils.UserModelDelegate; import org.keycloak.storage.ldap.LDAPConfig; import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPUtils; import org.keycloak.storage.ldap.idm.model.LDAPDn; import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.idm.query.Condition; import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery; import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder; import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper; import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapper; import org.keycloak.storage.ldap.mappers.membership.CommonLDAPGroupMapperConfig; import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; import org.keycloak.storage.ldap.mappers.membership.MembershipType; import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy; import org.keycloak.storage.user.SynchronizationResult; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements CommonLDAPGroupMapper { private static final Logger logger = Logger.getLogger(GroupLDAPStorageMapper.class); private final GroupMapperConfig config; private final GroupLDAPStorageMapperFactory factory; // Flag to avoid syncing multiple times per transaction private boolean syncFromLDAPPerformedInThisTransaction = false; public GroupLDAPStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider, GroupLDAPStorageMapperFactory factory) { super(mapperModel, ldapProvider); this.config = new GroupMapperConfig(mapperModel); this.factory = factory; } // CommonLDAPGroupMapper interface @Override public LDAPQuery createLDAPGroupQuery() { return createGroupQuery(); } @Override public CommonLDAPGroupMapperConfig getConfig() { return config; } // LDAP Group CRUD operations public LDAPQuery createGroupQuery() { LDAPQuery ldapQuery = new LDAPQuery(ldapProvider); // For now, use same search scope, which is configured "globally" and used for user's search. ldapQuery.setSearchScope(ldapProvider.getLdapIdentityStore().getConfig().getSearchScope()); String groupsDn = config.getGroupsDn(); ldapQuery.setSearchDn(groupsDn); Collection<String> groupObjectClasses = config.getGroupObjectClasses(ldapProvider); ldapQuery.addObjectClasses(groupObjectClasses); String customFilter = config.getCustomLdapFilter(); if (customFilter != null && customFilter.trim().length() > 0) { Condition customFilterCondition = new LDAPQueryConditionsBuilder().addCustomLDAPFilter(customFilter); ldapQuery.addWhereCondition(customFilterCondition); } ldapQuery.addReturningLdapAttribute(config.getGroupNameLdapAttribute()); ldapQuery.addReturningLdapAttribute(config.getMembershipLdapAttribute()); for (String groupAttr : config.getGroupAttributes()) { ldapQuery.addReturningLdapAttribute(groupAttr); } return ldapQuery; } public LDAPObject createLDAPGroup(String groupName, Map<String, Set<String>> additionalAttributes) { LDAPObject ldapGroup = LDAPUtils.createLDAPGroup(ldapProvider, groupName, config.getGroupNameLdapAttribute(), config.getGroupObjectClasses(ldapProvider), config.getGroupsDn(), additionalAttributes); logger.debugf("Creating group [%s] to LDAP with DN [%s]", groupName, ldapGroup.getDn().toString()); return ldapGroup; } public LDAPObject loadLDAPGroupByName(String groupName) { LDAPQuery ldapQuery = createGroupQuery(); Condition roleNameCondition = new LDAPQueryConditionsBuilder().equal(config.getGroupNameLdapAttribute(), groupName); ldapQuery.addWhereCondition(roleNameCondition); return ldapQuery.getFirstResult(); } protected Set<LDAPDn> getLDAPSubgroups(LDAPObject ldapGroup) { MembershipType membershipType = config.getMembershipTypeLdapAttribute(); return membershipType.getLDAPSubgroups(this, ldapGroup); } // Sync from Ldap to KC @Override public SynchronizationResult syncDataFromFederationProviderToKeycloak(RealmModel realm) { SynchronizationResult syncResult = new SynchronizationResult() { @Override public String getStatus() { return String.format("%d imported groups, %d updated groups, %d removed groups", getAdded(), getUpdated(), getRemoved()); } }; logger.debugf("Syncing groups from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getName()); // Get all LDAP groups List<LDAPObject> ldapGroups = getAllLDAPGroups(); // Convert to internal format Map<String, LDAPObject> ldapGroupsMap = new HashMap<>(); List<GroupTreeResolver.Group> ldapGroupsRep = new LinkedList<>(); String groupsRdnAttr = config.getGroupNameLdapAttribute(); for (LDAPObject ldapGroup : ldapGroups) { String groupName = ldapGroup.getAttributeAsString(groupsRdnAttr); Set<String> subgroupNames = new HashSet<>(); for (LDAPDn groupDn : getLDAPSubgroups(ldapGroup)) { subgroupNames.add(groupDn.getFirstRdnAttrValue()); } ldapGroupsRep.add(new GroupTreeResolver.Group(groupName, subgroupNames)); ldapGroupsMap.put(groupName, ldapGroup); } // Now we have list of LDAP groups. Let's form the tree (if needed) if (config.isPreserveGroupsInheritance()) { try { List<GroupTreeResolver.GroupTreeEntry> groupTrees = new GroupTreeResolver().resolveGroupTree(ldapGroupsRep); updateKeycloakGroupTree(realm, groupTrees, ldapGroupsMap, syncResult); } catch (GroupTreeResolver.GroupTreeResolveException gre) { throw new ModelException("Couldn't resolve groups from LDAP. Fix LDAP or skip preserve inheritance. Details: " + gre.getMessage(), gre); } } else { Set<String> visitedGroupIds = new HashSet<>(); // Just add flat structure of groups with all groups at top-level for (Map.Entry<String, LDAPObject> groupEntry : ldapGroupsMap.entrySet()) { String groupName = groupEntry.getKey(); GroupModel kcExistingGroup = KeycloakModelUtils.findGroupByPath(realm, "/" + groupName); if (kcExistingGroup != null) { updateAttributesOfKCGroup(kcExistingGroup, groupEntry.getValue()); syncResult.increaseUpdated(); visitedGroupIds.add(kcExistingGroup.getId()); } else { GroupModel kcGroup = realm.createGroup(groupName); updateAttributesOfKCGroup(kcGroup, groupEntry.getValue()); realm.moveGroup(kcGroup, null); syncResult.increaseAdded(); visitedGroupIds.add(kcGroup.getId()); } } // Possibly remove keycloak groups, which doesn't exists in LDAP if (config.isDropNonExistingGroupsDuringSync()) { dropNonExistingKcGroups(realm, syncResult, visitedGroupIds); } } syncFromLDAPPerformedInThisTransaction = true; return syncResult; } private void updateKeycloakGroupTree(RealmModel realm, List<GroupTreeResolver.GroupTreeEntry> groupTrees, Map<String, LDAPObject> ldapGroups, SynchronizationResult syncResult) { Set<String> visitedGroupIds = new HashSet<>(); for (GroupTreeResolver.GroupTreeEntry groupEntry : groupTrees) { updateKeycloakGroupTreeEntry(realm, groupEntry, ldapGroups, null, syncResult, visitedGroupIds); } // Possibly remove keycloak groups, which doesn't exists in LDAP if (config.isDropNonExistingGroupsDuringSync()) { dropNonExistingKcGroups(realm, syncResult, visitedGroupIds); } } private void updateKeycloakGroupTreeEntry(RealmModel realm, GroupTreeResolver.GroupTreeEntry groupTreeEntry, Map<String, LDAPObject> ldapGroups, GroupModel kcParent, SynchronizationResult syncResult, Set<String> visitedGroupIds) { String groupName = groupTreeEntry.getGroupName(); // Check if group already exists GroupModel kcGroup = null; Collection<GroupModel> subgroups = kcParent == null ? realm.getTopLevelGroups() : kcParent.getSubGroups(); for (GroupModel group : subgroups) { if (group.getName().equals(groupName)) { kcGroup = group; break; } } if (kcGroup != null) { logger.debugf("Updated Keycloak group '%s' from LDAP", kcGroup.getName()); updateAttributesOfKCGroup(kcGroup, ldapGroups.get(kcGroup.getName())); syncResult.increaseUpdated(); } else { kcGroup = realm.createGroup(groupTreeEntry.getGroupName()); if (kcParent == null) { realm.moveGroup(kcGroup, null); logger.debugf("Imported top-level group '%s' from LDAP", kcGroup.getName()); } else { realm.moveGroup(kcGroup, kcParent); logger.debugf("Imported group '%s' from LDAP as child of group '%s'", kcGroup.getName(), kcParent.getName()); } updateAttributesOfKCGroup(kcGroup, ldapGroups.get(kcGroup.getName())); syncResult.increaseAdded(); } visitedGroupIds.add(kcGroup.getId()); for (GroupTreeResolver.GroupTreeEntry childEntry : groupTreeEntry.getChildren()) { updateKeycloakGroupTreeEntry(realm, childEntry, ldapGroups, kcGroup, syncResult, visitedGroupIds); } } private void dropNonExistingKcGroups(RealmModel realm, SynchronizationResult syncResult, Set<String> visitedGroupIds) { // Remove keycloak groups, which doesn't exists in LDAP List<GroupModel> allGroups = realm.getGroups(); for (GroupModel kcGroup : allGroups) { if (!visitedGroupIds.contains(kcGroup.getId())) { logger.debugf("Removing Keycloak group '%s', which doesn't exist in LDAP", kcGroup.getName()); realm.removeGroup(kcGroup); syncResult.increaseRemoved(); } } } private void updateAttributesOfKCGroup(GroupModel kcGroup, LDAPObject ldapGroup) { Collection<String> groupAttributes = config.getGroupAttributes(); for (String attrName : groupAttributes) { Set<String> attrValues = ldapGroup.getAttributeAsSet(attrName); if (attrValues==null) { kcGroup.removeAttribute(attrName); } else { kcGroup.setAttribute(attrName, new LinkedList<>(attrValues)); } } } protected GroupModel findKcGroupByLDAPGroup(RealmModel realm, LDAPObject ldapGroup) { String groupNameAttr = config.getGroupNameLdapAttribute(); String groupName = ldapGroup.getAttributeAsString(groupNameAttr); if (config.isPreserveGroupsInheritance()) { // Override if better effectivity or different algorithm is needed List<GroupModel> groups = realm.getGroups(); for (GroupModel group : groups) { if (group.getName().equals(groupName)) { return group; } } return null; } else { // Without preserved inheritance, it's always top-level group return KeycloakModelUtils.findGroupByPath(realm, "/" + groupName); } } protected GroupModel findKcGroupOrSyncFromLDAP(RealmModel realm, LDAPObject ldapGroup, UserModel user) { GroupModel kcGroup = findKcGroupByLDAPGroup(realm, ldapGroup); if (kcGroup == null) { if (config.isPreserveGroupsInheritance()) { // Better to sync all groups from LDAP with preserved inheritance if (!syncFromLDAPPerformedInThisTransaction) { syncDataFromFederationProviderToKeycloak(realm); kcGroup = findKcGroupByLDAPGroup(realm, ldapGroup); } } else { String groupNameAttr = config.getGroupNameLdapAttribute(); String groupName = ldapGroup.getAttributeAsString(groupNameAttr); kcGroup = realm.createGroup(groupName); updateAttributesOfKCGroup(kcGroup, ldapGroup); realm.moveGroup(kcGroup, null); } // Could theoretically happen on some LDAP servers if 'memberof' style is used and 'memberof' attribute of user references non-existing group if (kcGroup == null) { String groupName = ldapGroup.getAttributeAsString(config.getGroupNameLdapAttribute()); logger.warnf("User '%s' is member of group '%s', which doesn't exists in LDAP", user.getUsername(), groupName); } } return kcGroup; } // Send LDAP query to retrieve all groups protected List<LDAPObject> getAllLDAPGroups() { LDAPQuery ldapGroupQuery = createGroupQuery(); return LDAPUtils.loadAllLDAPObjects(ldapGroupQuery, ldapProvider); } // Sync from Keycloak to LDAP @Override public SynchronizationResult syncDataFromKeycloakToFederationProvider(RealmModel realm) { SynchronizationResult syncResult = new SynchronizationResult() { @Override public String getStatus() { return String.format("%d groups imported to LDAP, %d groups updated to LDAP, %d groups removed from LDAP", getAdded(), getUpdated(), getRemoved()); } }; if (config.getMode() != LDAPGroupMapperMode.LDAP_ONLY) { logger.warnf("Ignored sync for federation mapper '%s' as it's mode is '%s'", mapperModel.getName(), config.getMode().toString()); return syncResult; } logger.debugf("Syncing groups from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getName()); // Query existing LDAP groups LDAPQuery ldapQuery = createGroupQuery(); List<LDAPObject> ldapGroups = ldapQuery.getResultList(); // Convert them to Map<String, LDAPObject> Map<String, LDAPObject> ldapGroupsMap = new HashMap<>(); String groupsRdnAttr = config.getGroupNameLdapAttribute(); for (LDAPObject ldapGroup : ldapGroups) { String groupName = ldapGroup.getAttributeAsString(groupsRdnAttr); ldapGroupsMap.put(groupName, ldapGroup); } // Map to track all LDAP groups also exists in Keycloak Set<String> ldapGroupNames = new HashSet<>(); // Create or update KC groups to LDAP including their attributes for (GroupModel kcGroup : realm.getTopLevelGroups()) { processLdapGroupSyncToLDAP(kcGroup, ldapGroupsMap, ldapGroupNames, syncResult); } // If dropNonExisting, then drop all groups, which doesn't exist in KC from LDAP as well if (config.isDropNonExistingGroupsDuringSync()) { Set<String> copy = new HashSet<>(ldapGroupsMap.keySet()); for (String groupName : copy) { if (!ldapGroupNames.contains(groupName)) { LDAPObject ldapGroup = ldapGroupsMap.remove(groupName); ldapProvider.getLdapIdentityStore().remove(ldapGroup); syncResult.increaseRemoved(); } } } // Finally process memberships, if (config.isPreserveGroupsInheritance()) { for (GroupModel kcGroup : realm.getTopLevelGroups()) { processLdapGroupMembershipsSyncToLDAP(kcGroup, ldapGroupsMap); } } return syncResult; } // For given kcGroup check if it exists in LDAP (map) by name // If not, create it in LDAP including attributes. Otherwise update attributes in LDAP. // Process this recursively for all subgroups of KC group private void processLdapGroupSyncToLDAP(GroupModel kcGroup, Map<String, LDAPObject> ldapGroupsMap, Set<String> ldapGroupNames, SynchronizationResult syncResult) { String groupName = kcGroup.getName(); // extract group attributes to be updated to LDAP Map<String, Set<String>> supportedLdapAttributes = new HashMap<>(); for (String attrName : config.getGroupAttributes()) { List<String> kcAttrValues = kcGroup.getAttribute(attrName); Set<String> attrValues2 = (kcAttrValues == null || kcAttrValues.isEmpty()) ? null : new HashSet<>(kcAttrValues); supportedLdapAttributes.put(attrName, attrValues2); } LDAPObject ldapGroup = ldapGroupsMap.get(groupName); if (ldapGroup == null) { ldapGroup = createLDAPGroup(groupName, supportedLdapAttributes); syncResult.increaseAdded(); } else { for (Map.Entry<String, Set<String>> attrEntry : supportedLdapAttributes.entrySet()) { ldapGroup.setAttribute(attrEntry.getKey(), attrEntry.getValue()); } ldapProvider.getLdapIdentityStore().update(ldapGroup); syncResult.increaseUpdated(); } ldapGroupsMap.put(groupName, ldapGroup); ldapGroupNames.add(groupName); // process KC subgroups for (GroupModel kcSubgroup : kcGroup.getSubGroups()) { processLdapGroupSyncToLDAP(kcSubgroup, ldapGroupsMap, ldapGroupNames, syncResult); } } // Sync memberships update. Update memberships of group in LDAP based on subgroups from KC. Do it recursively private void processLdapGroupMembershipsSyncToLDAP(GroupModel kcGroup, Map<String, LDAPObject> ldapGroupsMap) { LDAPObject ldapGroup = ldapGroupsMap.get(kcGroup.getName()); Set<LDAPDn> toRemoveSubgroupsDNs = getLDAPSubgroups(ldapGroup); String membershipUserLdapAttrName = getMembershipUserLdapAttribute(); // Not applicable for groups, but needs to be here // Add LDAP subgroups, which are KC subgroups Set<GroupModel> kcSubgroups = kcGroup.getSubGroups(); for (GroupModel kcSubgroup : kcSubgroups) { LDAPObject ldapSubgroup = ldapGroupsMap.get(kcSubgroup.getName()); LDAPUtils.addMember(ldapProvider, MembershipType.DN, config.getMembershipLdapAttribute(), membershipUserLdapAttrName, ldapGroup, ldapSubgroup, false); toRemoveSubgroupsDNs.remove(ldapSubgroup.getDn()); } // Remove LDAP subgroups, which are not members in KC anymore for (LDAPDn toRemoveDN : toRemoveSubgroupsDNs) { LDAPObject fakeGroup = new LDAPObject(); fakeGroup.setDn(toRemoveDN); LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, config.getMembershipLdapAttribute(), membershipUserLdapAttrName, ldapGroup, fakeGroup); } // Update group to LDAP if (!kcGroup.getSubGroups().isEmpty() || !toRemoveSubgroupsDNs.isEmpty()) { ldapProvider.getLdapIdentityStore().update(ldapGroup); } for (GroupModel kcSubgroup : kcGroup.getSubGroups()) { processLdapGroupMembershipsSyncToLDAP(kcSubgroup, ldapGroupsMap); } } // group-user membership operations @Override public List<UserModel> getGroupMembers(RealmModel realm, GroupModel kcGroup, int firstResult, int maxResults) { LDAPObject ldapGroup = loadLDAPGroupByName(kcGroup.getName()); if (ldapGroup == null) { return Collections.emptyList(); } MembershipType membershipType = config.getMembershipTypeLdapAttribute(); return membershipType.getGroupMembers(realm, this, ldapGroup, firstResult, maxResults); } public void addGroupMappingInLDAP(RealmModel realm, String groupName, LDAPObject ldapUser) { LDAPObject ldapGroup = loadLDAPGroupByName(groupName); if (ldapGroup == null) { syncDataFromKeycloakToFederationProvider(realm); ldapGroup = loadLDAPGroupByName(groupName); } String membershipUserLdapAttrName = getMembershipUserLdapAttribute(); LDAPUtils.addMember(ldapProvider, config.getMembershipTypeLdapAttribute(), config.getMembershipLdapAttribute(), membershipUserLdapAttrName, ldapGroup, ldapUser, true); } public void deleteGroupMappingInLDAP(LDAPObject ldapUser, LDAPObject ldapGroup) { String membershipUserLdapAttrName = getMembershipUserLdapAttribute(); LDAPUtils.deleteMember(ldapProvider, config.getMembershipTypeLdapAttribute(), config.getMembershipLdapAttribute(), membershipUserLdapAttrName, ldapGroup, ldapUser); } protected List<LDAPObject> getLDAPGroupMappings(LDAPObject ldapUser) { String strategyKey = config.getUserGroupsRetrieveStrategy(); UserRolesRetrieveStrategy strategy = factory.getUserGroupsRetrieveStrategy(strategyKey); LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); return strategy.getLDAPRoleMappings(this, ldapUser, ldapConfig); } @Override public void beforeLDAPQuery(LDAPQuery query) { String strategyKey = config.getUserGroupsRetrieveStrategy(); UserRolesRetrieveStrategy strategy = factory.getUserGroupsRetrieveStrategy(strategyKey); strategy.beforeUserLDAPQuery(query); } @Override public UserModel proxy(LDAPObject ldapUser, UserModel delegate, RealmModel realm) { final LDAPGroupMapperMode mode = config.getMode(); // For IMPORT mode, all operations are performed against local DB if (mode == LDAPGroupMapperMode.IMPORT) { return delegate; } else { return new LDAPGroupMappingsUserDelegate(realm, delegate, ldapUser); } } @Override public void onRegisterUserToLDAP(LDAPObject ldapUser, UserModel localUser, RealmModel realm) { } @Override public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) { LDAPGroupMapperMode mode = config.getMode(); // For now, import LDAP group mappings just during create if (mode == LDAPGroupMapperMode.IMPORT && isCreate) { List<LDAPObject> ldapGroups = getLDAPGroupMappings(ldapUser); // Import role mappings from LDAP into Keycloak DB for (LDAPObject ldapGroup : ldapGroups) { GroupModel kcGroup = findKcGroupOrSyncFromLDAP(realm, ldapGroup, user); if (kcGroup != null) { logger.debugf("User '%s' joins group '%s' during import from LDAP", user.getUsername(), kcGroup.getName()); user.joinGroup(kcGroup); } } } } protected String getMembershipUserLdapAttribute() { LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); return config.getMembershipUserLdapAttribute(ldapConfig); } public class LDAPGroupMappingsUserDelegate extends UserModelDelegate { private final RealmModel realm; private final LDAPObject ldapUser; // Avoid loading group mappings from LDAP more times per-request private Set<GroupModel> cachedLDAPGroupMappings; public LDAPGroupMappingsUserDelegate(RealmModel realm, UserModel user, LDAPObject ldapUser) { super(user); this.realm = realm; this.ldapUser = ldapUser; } @Override public boolean hasRole(RoleModel role) { return super.hasRole(role) || RoleUtils.hasRoleFromGroup(getGroups(), role, true); } @Override public Set<GroupModel> getGroups() { Set<GroupModel> ldapGroupMappings = getLDAPGroupMappingsConverted(); if (config.getMode() == LDAPGroupMapperMode.LDAP_ONLY) { // Use just group mappings from LDAP return ldapGroupMappings; } else { // Merge mappings from both DB and LDAP Set<GroupModel> modelGroupMappings = super.getGroups(); ldapGroupMappings.addAll(modelGroupMappings); return ldapGroupMappings; } } @Override public void joinGroup(GroupModel group) { if (config.getMode() == LDAPGroupMapperMode.LDAP_ONLY) { // We need to create new role mappings in LDAP cachedLDAPGroupMappings = null; addGroupMappingInLDAP(realm, group.getName(), ldapUser); } else { super.joinGroup(group); } } @Override public void leaveGroup(GroupModel group) { LDAPQuery ldapQuery = createGroupQuery(); LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder(); Condition roleNameCondition = conditionsBuilder.equal(config.getGroupNameLdapAttribute(), group.getName()); String membershipUserLdapAttrName = getMembershipUserLdapAttribute(); String membershipUserAttr = LDAPUtils.getMemberValueOfChildObject(ldapUser, config.getMembershipTypeLdapAttribute(), membershipUserLdapAttrName); Condition membershipCondition = conditionsBuilder.equal(config.getMembershipLdapAttribute(), membershipUserAttr); ldapQuery.addWhereCondition(roleNameCondition).addWhereCondition(membershipCondition); LDAPObject ldapGroup = ldapQuery.getFirstResult(); if (ldapGroup == null) { // Group mapping doesn't exist in LDAP. For LDAP_ONLY mode, we don't need to do anything. For READ_ONLY, delete it in local DB. if (config.getMode() == LDAPGroupMapperMode.READ_ONLY) { super.leaveGroup(group); } } else { // Group mappings exists in LDAP. For LDAP_ONLY mode, we can just delete it in LDAP. For READ_ONLY we can't delete it -> throw error if (config.getMode() == LDAPGroupMapperMode.READ_ONLY) { throw new ModelException("Not possible to delete LDAP group mappings as mapper mode is READ_ONLY"); } else { // Delete ldap role mappings cachedLDAPGroupMappings = null; deleteGroupMappingInLDAP(ldapUser, ldapGroup); } } } @Override public boolean isMemberOf(GroupModel group) { Set<GroupModel> ldapGroupMappings = getGroups(); return ldapGroupMappings.contains(group); } protected Set<GroupModel> getLDAPGroupMappingsConverted() { if (cachedLDAPGroupMappings != null) { return new HashSet<>(cachedLDAPGroupMappings); } List<LDAPObject> ldapGroups = getLDAPGroupMappings(ldapUser); Set<GroupModel> result = new HashSet<>(); for (LDAPObject ldapGroup : ldapGroups) { GroupModel kcGroup = findKcGroupOrSyncFromLDAP(realm, ldapGroup, this); if (kcGroup != null) { result.add(kcGroup); } } cachedLDAPGroupMappings = new HashSet<>(result); return result; } } }