diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index 714a97a16b..698a392c12 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -206,25 +206,9 @@ public class LDAPFederationProvider implements UserFederationProvider { return Collections.emptyList(); } - public List loadUsersByLDAPDns(Collection userDns, RealmModel realm) { - // We have dns of users, who are members of our group. Load them now - LDAPQuery query = LDAPUtils.createQueryForUserSearch(this, realm); - LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder(); - Condition[] orSubconditions = new Condition[userDns.size()]; - int index = 0; - for (LDAPDn userDn : userDns) { - Condition condition = conditionsBuilder.equal(userDn.getFirstRdnAttrName(), userDn.getFirstRdnAttrValue()); - orSubconditions[index] = condition; - index++; - } - Condition orCondition = conditionsBuilder.orCondition(orSubconditions); - query.addWhereCondition(orCondition); - List ldapUsers = query.getResultList(); - - // We have ldapUsers, Need to load users from KC DB or import them here - List result = new LinkedList<>(); - for (LDAPObject ldapUser : ldapUsers) { - String username = LDAPUtils.getUsername(ldapUser, getLdapIdentityStore().getConfig()); + public List loadUsersByUsernames(List usernames, RealmModel realm) { + List result = new ArrayList<>(); + for (String username : usernames) { UserModel kcUser = session.users().getUserByUsername(username, realm); if (!model.getId().equals(kcUser.getFederationLink())) { logger.warnf("Incorrect federation provider of user %s" + kcUser.getUsername()); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java index 9b08f5fe3b..2a79a48673 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapper.java @@ -75,4 +75,12 @@ public abstract class AbstractLDAPFederationMapper { String paramm = mapperModel.getConfig().get(paramName); return Boolean.parseBoolean(paramm); } + + public LDAPFederationProvider getLdapProvider() { + return ldapProvider; + } + + public RealmModel getRealm() { + return realm; + } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java index 624ed3b07d..cee0c73e15 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/MembershipType.java @@ -1,5 +1,24 @@ package org.keycloak.federation.ldap.mappers.membership; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.keycloak.federation.ldap.LDAPConfig; +import org.keycloak.federation.ldap.LDAPFederationProvider; +import org.keycloak.federation.ldap.LDAPUtils; +import org.keycloak.federation.ldap.idm.model.LDAPDn; +import org.keycloak.federation.ldap.idm.model.LDAPObject; +import org.keycloak.federation.ldap.idm.query.Condition; +import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery; +import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder; +import org.keycloak.federation.ldap.mappers.membership.group.GroupLDAPFederationMapper; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + /** * @author Marek Posolda */ @@ -8,10 +27,114 @@ public enum MembershipType { /** * Used if LDAP role has it's members declared in form of their full DN. For example ( "member: uid=john,ou=users,dc=example,dc=com" ) */ - DN, + DN { + + @Override + public Set getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) { + CommonLDAPGroupMapperConfig config = groupMapper.getConfig(); + return getLDAPMembersWithParent(ldapGroup, config.getMembershipLdapAttribute(), LDAPDn.fromString(config.getLDAPGroupsDn())); + } + + // Get just those members of specified group, which are descendants of "requiredParentDn" + protected Set getLDAPMembersWithParent(LDAPObject ldapGroup, String membershipLdapAttribute, LDAPDn requiredParentDn) { + Set allMemberships = LDAPUtils.getExistingMemberships(membershipLdapAttribute, ldapGroup); + + // Filter and keep just groups + Set result = new HashSet<>(); + for (String membership : allMemberships) { + LDAPDn childDn = LDAPDn.fromString(membership); + if (childDn.isDescendantOf(requiredParentDn)) { + result.add(childDn); + } + } + return result; + } + + @Override + public List getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) { + RealmModel realm = groupMapper.getRealm(); + LDAPFederationProvider ldapProvider = groupMapper.getLdapProvider(); + CommonLDAPGroupMapperConfig config = groupMapper.getConfig(); + + LDAPDn usersDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn()); + Set userDns = getLDAPMembersWithParent(ldapGroup, config.getMembershipLdapAttribute(), usersDn); + + if (userDns == null) { + return Collections.emptyList(); + } + + if (userDns.size() <= firstResult) { + return Collections.emptyList(); + } + + List dns = new ArrayList<>(userDns); + int max = Math.min(dns.size(), firstResult + maxResults); + dns = dns.subList(firstResult, max); + + // If usernameAttrName is same like DN, we can just retrieve usernames from DNs + List usernames = new LinkedList<>(); + LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); + if (ldapConfig.getUsernameLdapAttribute().equals(ldapConfig.getRdnLdapAttribute())) { + for (LDAPDn userDn : dns) { + String username = userDn.getFirstRdnAttrValue(); + usernames.add(username); + } + } else { + LDAPQuery query = LDAPUtils.createQueryForUserSearch(ldapProvider, realm); + LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder(); + Condition[] orSubconditions = new Condition[dns.size()]; + int index = 0; + for (LDAPDn userDn : dns) { + Condition condition = conditionsBuilder.equal(userDn.getFirstRdnAttrName(), userDn.getFirstRdnAttrValue()); + orSubconditions[index] = condition; + index++; + } + Condition orCondition = conditionsBuilder.orCondition(orSubconditions); + query.addWhereCondition(orCondition); + List ldapUsers = query.getResultList(); + for (LDAPObject ldapUser : ldapUsers) { + String username = LDAPUtils.getUsername(ldapUser, ldapConfig); + usernames.add(username); + } + } + + // We have dns of users, who are members of our group. Load them now + return ldapProvider.loadUsersByUsernames(usernames, realm); + } + + }, + /** * Used if LDAP role has it's members declared in form of pure user uids. For example ( "memberUid: john" ) */ - UID + UID { + + // Group inheritance not supported for this config + @Override + public Set getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) { + return Collections.emptySet(); + } + + @Override + public List getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) { + String memberAttrName = groupMapper.getConfig().getMembershipLdapAttribute(); + Set memberUids = LDAPUtils.getExistingMemberships(memberAttrName, ldapGroup); + + if (memberUids == null || memberUids.size() <= firstResult) { + return Collections.emptyList(); + } + + List uids = new ArrayList<>(memberUids); + int max = Math.min(memberUids.size(), firstResult + maxResults); + uids = uids.subList(firstResult, max); + + return groupMapper.getLdapProvider().loadUsersByUsernames(uids, groupMapper.getRealm()); + } + + }; + + public abstract Set getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup); + + public abstract List getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults); } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java index 1d2a407604..911e2920e9 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java @@ -1,6 +1,5 @@ package org.keycloak.federation.ldap.mappers.membership.group; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -27,8 +26,6 @@ import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy import org.keycloak.models.GroupModel; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleContainerModel; -import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserModel; @@ -115,22 +112,8 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl } protected Set getLDAPSubgroups(LDAPObject ldapGroup) { - return getLDAPMembersWithParent(ldapGroup, LDAPDn.fromString(config.getGroupsDn())); - } - - // Get just those members of specified group, which are descendants of "requiredParentDn" - protected Set getLDAPMembersWithParent(LDAPObject ldapGroup, LDAPDn requiredParentDn) { - Set allMemberships = LDAPUtils.getExistingMemberships(config.getMembershipLdapAttribute(), ldapGroup); - - // Filter and keep just groups - Set result = new HashSet<>(); - for (String membership : allMemberships) { - LDAPDn childDn = LDAPDn.fromString(membership); - if (childDn.isDescendantOf(requiredParentDn)) { - result.add(childDn); - } - } - return result; + MembershipType membershipType = config.getMembershipTypeLdapAttribute(); + return membershipType.getLDAPSubgroups(this, ldapGroup); } @@ -461,23 +444,8 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl return Collections.emptyList(); } - LDAPDn usersDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn()); - Set userDns = getLDAPMembersWithParent(ldapGroup, usersDn); - - if (userDns == null) { - return Collections.emptyList(); - } - - if (userDns.size() <= firstResult) { - return Collections.emptyList(); - } - - List dns = new ArrayList<>(userDns); - int max = Math.min(dns.size(), firstResult + maxResults); - dns = dns.subList(firstResult, max); - - // We have dns of users, who are members of our group. Load them now - return ldapProvider.loadUsersByLDAPDns(dns, realm); + MembershipType membershipType = config.getMembershipTypeLdapAttribute(); + return membershipType.getGroupMembers(this, ldapGroup, firstResult, maxResults); } public void addGroupMappingInLDAP(String groupName, LDAPObject ldapUser) { diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java index 1e06fa995b..464cc0d92e 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java @@ -11,6 +11,7 @@ import org.keycloak.federation.ldap.LDAPConfig; import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory; +import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig; import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode; import org.keycloak.federation.ldap.mappers.membership.MembershipType; import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy; @@ -170,6 +171,13 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException { checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel); checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel); + + String mt = mapperModel.getConfig().get(CommonLDAPGroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE); + MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt); + boolean preserveGroupInheritance = Boolean.parseBoolean(mapperModel.getConfig().get(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE)); + if (preserveGroupInheritance && membershipType != MembershipType.DN) { + throw new MapperConfigValidationException("Not possible to preserve group inheritance and use UID membership type together"); + } } @Override diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java index 33cb2840a4..f7e17f6312 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java @@ -59,6 +59,9 @@ public class LDAPGroupMapperSyncTest { // Add group mapper FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); + // Remove all LDAP groups + FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); + // Add some groups for testing LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java index 4aaac4148b..8778735291 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java @@ -64,6 +64,9 @@ public class LDAPGroupMapperTest { // Add group mapper FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); + // Remove all LDAP groups + FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper"); + // Add some groups for testing LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");