KEYCLOAK-2154 Group mapper fixes

This commit is contained in:
mposolda 2015-12-22 12:29:17 +01:00
parent 5403296ac6
commit 0c293089c3
7 changed files with 154 additions and 57 deletions

View file

@ -206,25 +206,9 @@ public class LDAPFederationProvider implements UserFederationProvider {
return Collections.emptyList(); return Collections.emptyList();
} }
public List<UserModel> loadUsersByLDAPDns(Collection<LDAPDn> userDns, RealmModel realm) { public List<UserModel> loadUsersByUsernames(List<String> usernames, RealmModel realm) {
// We have dns of users, who are members of our group. Load them now List<UserModel> result = new ArrayList<>();
LDAPQuery query = LDAPUtils.createQueryForUserSearch(this, realm); for (String username : usernames) {
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<LDAPObject> ldapUsers = query.getResultList();
// We have ldapUsers, Need to load users from KC DB or import them here
List<UserModel> result = new LinkedList<>();
for (LDAPObject ldapUser : ldapUsers) {
String username = LDAPUtils.getUsername(ldapUser, getLdapIdentityStore().getConfig());
UserModel kcUser = session.users().getUserByUsername(username, realm); UserModel kcUser = session.users().getUserByUsername(username, realm);
if (!model.getId().equals(kcUser.getFederationLink())) { if (!model.getId().equals(kcUser.getFederationLink())) {
logger.warnf("Incorrect federation provider of user %s" + kcUser.getUsername()); logger.warnf("Incorrect federation provider of user %s" + kcUser.getUsername());

View file

@ -75,4 +75,12 @@ public abstract class AbstractLDAPFederationMapper {
String paramm = mapperModel.getConfig().get(paramName); String paramm = mapperModel.getConfig().get(paramName);
return Boolean.parseBoolean(paramm); return Boolean.parseBoolean(paramm);
} }
public LDAPFederationProvider getLdapProvider() {
return ldapProvider;
}
public RealmModel getRealm() {
return realm;
}
} }

View file

@ -1,5 +1,24 @@
package org.keycloak.federation.ldap.mappers.membership; 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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@ -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" ) * 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<LDAPDn> 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<LDAPDn> getLDAPMembersWithParent(LDAPObject ldapGroup, String membershipLdapAttribute, LDAPDn requiredParentDn) {
Set<String> allMemberships = LDAPUtils.getExistingMemberships(membershipLdapAttribute, ldapGroup);
// Filter and keep just groups
Set<LDAPDn> result = new HashSet<>();
for (String membership : allMemberships) {
LDAPDn childDn = LDAPDn.fromString(membership);
if (childDn.isDescendantOf(requiredParentDn)) {
result.add(childDn);
}
}
return result;
}
@Override
public List<UserModel> 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<LDAPDn> userDns = getLDAPMembersWithParent(ldapGroup, config.getMembershipLdapAttribute(), usersDn);
if (userDns == null) {
return Collections.emptyList();
}
if (userDns.size() <= firstResult) {
return Collections.emptyList();
}
List<LDAPDn> 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<String> 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<LDAPObject> 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" ) * 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<LDAPDn> getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup) {
return Collections.emptySet();
}
@Override
public List<UserModel> getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults) {
String memberAttrName = groupMapper.getConfig().getMembershipLdapAttribute();
Set<String> memberUids = LDAPUtils.getExistingMemberships(memberAttrName, ldapGroup);
if (memberUids == null || memberUids.size() <= firstResult) {
return Collections.emptyList();
}
List<String> 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<LDAPDn> getLDAPSubgroups(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup);
public abstract List<UserModel> getGroupMembers(GroupLDAPFederationMapper groupMapper, LDAPObject ldapGroup, int firstResult, int maxResults);
} }

View file

@ -1,6 +1,5 @@
package org.keycloak.federation.ldap.mappers.membership.group; package org.keycloak.federation.ldap.mappers.membership.group;
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;
@ -27,8 +26,6 @@ import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -115,22 +112,8 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
} }
protected Set<LDAPDn> getLDAPSubgroups(LDAPObject ldapGroup) { protected Set<LDAPDn> getLDAPSubgroups(LDAPObject ldapGroup) {
return getLDAPMembersWithParent(ldapGroup, LDAPDn.fromString(config.getGroupsDn())); MembershipType membershipType = config.getMembershipTypeLdapAttribute();
} return membershipType.getLDAPSubgroups(this, ldapGroup);
// Get just those members of specified group, which are descendants of "requiredParentDn"
protected Set<LDAPDn> getLDAPMembersWithParent(LDAPObject ldapGroup, LDAPDn requiredParentDn) {
Set<String> allMemberships = LDAPUtils.getExistingMemberships(config.getMembershipLdapAttribute(), ldapGroup);
// Filter and keep just groups
Set<LDAPDn> result = new HashSet<>();
for (String membership : allMemberships) {
LDAPDn childDn = LDAPDn.fromString(membership);
if (childDn.isDescendantOf(requiredParentDn)) {
result.add(childDn);
}
}
return result;
} }
@ -461,23 +444,8 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
return Collections.emptyList(); return Collections.emptyList();
} }
LDAPDn usersDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn()); MembershipType membershipType = config.getMembershipTypeLdapAttribute();
Set<LDAPDn> userDns = getLDAPMembersWithParent(ldapGroup, usersDn); return membershipType.getGroupMembers(this, ldapGroup, firstResult, maxResults);
if (userDns == null) {
return Collections.emptyList();
}
if (userDns.size() <= firstResult) {
return Collections.emptyList();
}
List<LDAPDn> 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);
} }
public void addGroupMappingInLDAP(String groupName, LDAPObject ldapUser) { public void addGroupMappingInLDAP(String groupName, LDAPObject ldapUser) {

View file

@ -11,6 +11,7 @@ import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper; import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory; 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.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType; import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy; import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
@ -170,6 +171,13 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException { public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel); checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", 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 @Override

View file

@ -59,6 +59,9 @@ public class LDAPGroupMapperSyncTest {
// Add group mapper // Add group mapper
FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);
// Remove all LDAP groups
FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper");
// Add some groups for testing // Add some groups for testing
LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description");
LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");

View file

@ -64,6 +64,9 @@ public class LDAPGroupMapperTest {
// Add group mapper // Add group mapper
FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName); FederationTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);
// Remove all LDAP groups
FederationTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper");
// Add some groups for testing // Add some groups for testing
LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description"); LDAPObject group1 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description");
LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); LDAPObject group11 = FederationTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");