mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
Merge b66d8d151b into 94dcc24a8d
This commit is contained in:
commit
245b03e7cb
3 changed files with 91 additions and 1 deletions
|
|
@ -17,11 +17,14 @@
|
|||
|
||||
package org.keycloak.storage.ldap.mappers.membership.group;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -582,10 +585,53 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
|||
}
|
||||
|
||||
String strategyKey = config.getUserGroupsRetrieveStrategy();
|
||||
|
||||
if (GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY.equals(strategyKey)) {
|
||||
return getGroupMembersRecursively(realm, ldapGroup, firstResult, maxResults);
|
||||
}
|
||||
|
||||
UserRolesRetrieveStrategy strategy = factory.getUserGroupsRetrieveStrategy(strategyKey);
|
||||
return strategy.getLDAPRoleMembers(realm, this, ldapGroup, firstResult, maxResults);
|
||||
}
|
||||
|
||||
private List<UserModel> getGroupMembersRecursively(RealmModel realm, LDAPObject ldapGroup, int firstResult, int maxResults) {
|
||||
MembershipType membershipType = config.getMembershipTypeLdapAttribute();
|
||||
|
||||
Map<String, LDAPObject> groupClosure = new LinkedHashMap<>();
|
||||
Deque<LDAPObject> pending = new ArrayDeque<>();
|
||||
groupClosure.put(ldapGroup.getDn().toString(), ldapGroup);
|
||||
pending.add(ldapGroup);
|
||||
|
||||
while (!pending.isEmpty()) {
|
||||
LDAPObject currentGroup = pending.poll();
|
||||
for (LDAPDn subGroupDn : getLDAPSubgroups(currentGroup)) {
|
||||
String subGroupDnString = subGroupDn.toString();
|
||||
if (groupClosure.containsKey(subGroupDnString)) {
|
||||
continue;
|
||||
}
|
||||
String subGroupName = subGroupDn.getFirstRdn().getAttrValue(config.getGroupNameLdapAttribute());
|
||||
LDAPObject subGroup = subGroupName == null ? null : loadLDAPGroupByName(subGroupName);
|
||||
if (subGroup == null) {
|
||||
continue;
|
||||
}
|
||||
groupClosure.put(subGroupDnString, subGroup);
|
||||
pending.add(subGroup);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, UserModel> members = new LinkedHashMap<>();
|
||||
for (LDAPObject group : groupClosure.values()) {
|
||||
for (UserModel member : membershipType.getGroupMembers(realm, this, group, 0, Integer.MAX_VALUE)) {
|
||||
members.putIfAbsent(member.getId(), member);
|
||||
}
|
||||
}
|
||||
|
||||
return members.values().stream()
|
||||
.skip(Math.max(firstResult, 0))
|
||||
.limit(maxResults < 0 ? Long.MAX_VALUE : maxResults)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void addGroupMappingInLDAP(RealmModel realm, GroupModel kcGroup, LDAPObject ldapUser) {
|
||||
String groupName = kcGroup.getName();
|
||||
LDAPObject ldapGroup = loadLDAPGroupByName(groupName);
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
|
|||
String groupRetrieversHelpText = "Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. " +
|
||||
"GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user. Or from the other attribute specified by 'Member-Of LDAP Attribute' . ";
|
||||
if (isActiveDirectory) {
|
||||
groupRetrieversHelpText = groupRetrieversHelpText + "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that groups of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.";
|
||||
groupRetrieversHelpText = groupRetrieversHelpText + "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that groups of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension. When this strategy is selected, listing the members of a group also returns the members of its nested groups.";
|
||||
} else {
|
||||
// Option should be available just for the Active Directory
|
||||
groupRetrievers.remove(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY);
|
||||
|
|
|
|||
|
|
@ -1130,4 +1130,48 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest {
|
|||
appRealm.updateComponent(mapperModel);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test12_recursiveGroupMemberRetrieval() {
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
|
||||
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ctx.getLdapProvider(), appRealm);
|
||||
|
||||
LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1");
|
||||
LDAPObject group11 = groupMapper.loadLDAPGroupByName("group11");
|
||||
LDAPObject group12 = groupMapper.loadLDAPGroupByName("group12");
|
||||
|
||||
LDAPObject user1 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "recuser1", "Rec", "One", "recuser1@email.org", null, "1");
|
||||
LDAPObject user2 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "recuser2", "Rec", "Two", "recuser2@email.org", null, "2");
|
||||
LDAPObject user3 = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "recuser3", "Rec", "Three", "recuser3@email.org", null, "3");
|
||||
|
||||
LDAPUtils.addMember(ctx.getLdapProvider(), MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, user1);
|
||||
LDAPUtils.addMember(ctx.getLdapProvider(), MembershipType.DN, LDAPConstants.MEMBER, "not-used", group11, user2);
|
||||
LDAPUtils.addMember(ctx.getLdapProvider(), MembershipType.DN, LDAPConstants.MEMBER, "not-used", group12, user3);
|
||||
|
||||
LDAPTestUtils.updateConfigOptions(mapperModel,
|
||||
GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString(),
|
||||
GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY);
|
||||
appRealm.updateComponent(mapperModel);
|
||||
});
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
GroupModel group1 = KeycloakModelUtils.findGroupByPath(session, appRealm, "/group1");
|
||||
GroupModel group11 = KeycloakModelUtils.findGroupByPath(session, appRealm, "/group1/group11");
|
||||
|
||||
List<String> group11Members = session.users().getGroupMembersStream(appRealm, group11, 0, 10)
|
||||
.map(UserModel::getUsername).collect(Collectors.toList());
|
||||
assertThat(group11Members, containsInAnyOrder("recuser2"));
|
||||
|
||||
List<String> group1Members = session.users().getGroupMembersStream(appRealm, group1, 0, 10)
|
||||
.map(UserModel::getUsername).collect(Collectors.toList());
|
||||
assertThat(group1Members, containsInAnyOrder("recuser1", "recuser2", "recuser3"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue