memberOf attribute empty or values with a DN that does not match the role base DN fetches all roles

Closes #41842

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2025-08-14 12:16:16 -03:00 committed by GitHub
parent 0e23856dce
commit 56da6c4b7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 100 additions and 13 deletions

View File

@ -193,8 +193,12 @@ public class LDAPObject {
// Case-insensitive. Return null if there is not value of attribute with given name or set with all values otherwise
public Set<String> getAttributeAsSet(String name) {
return getAttributeAsSetOrDefault(name, null);
}
public Set<String> getAttributeAsSetOrDefault(String name, Set<String> defaultValue) {
Map.Entry<String, Set<String>> entry = lowerCasedAttributes.get(name.toLowerCase());
return (entry == null) ? null : new LinkedHashSet<>(entry.getValue());
return (entry == null) ? defaultValue : new LinkedHashSet<>(entry.getValue());
}
public boolean isRangeComplete(String name) {

View File

@ -94,27 +94,27 @@ public interface UserRolesRetrieveStrategy {
@Override
public List<LDAPObject> getLDAPRoleMappings(CommonLDAPGroupMapper roleOrGroupMapper, LDAPObject ldapUser, LDAPConfig ldapConfig) {
Set<String> memberOfValues = ldapUser.getAttributeAsSet(roleOrGroupMapper.getConfig().getMemberOfLdapAttribute());
if (memberOfValues == null || memberOfValues.isEmpty()) {
return Collections.emptyList();
}
try (LDAPQuery ldapQuery = roleOrGroupMapper.createLDAPGroupQuery()) {
String rdnAttr = roleOrGroupMapper.getConfig().getLDAPGroupNameLdapAttribute();
CommonLDAPGroupMapperConfig config = roleOrGroupMapper.getConfig();
String rdnAttr = config.getLDAPGroupNameLdapAttribute();
LDAPQueryConditionsBuilder conditionBuilder = new LDAPQueryConditionsBuilder();
Set<String> memberOfValues = ldapUser.getAttributeAsSetOrDefault(config.getMemberOfLdapAttribute(), Set.of());
// load only those groups/roles the user is memberOf
// we do this by query to apply defined custom filters
final Condition[] conditions = memberOfValues.stream()
// and make sure the values of memberOf have the role/group base DN as its parent
Condition[] conditions = memberOfValues.stream()
.map(LDAPDn::fromString)
.filter(roleDN -> roleDN.isDescendantOf(LDAPDn.fromString(roleOrGroupMapper.getConfig().getLDAPGroupsDn())))
.filter(roleDN -> roleDN.isDescendantOf(LDAPDn.fromString(config.getLDAPGroupsDn())))
.map(roleDN -> conditionBuilder.equal(rdnAttr, roleDN.getFirstRdn().getAttrValue(rdnAttr)))
.toArray(Condition[]::new);
if (conditions.length > 0) {
ldapQuery.addWhereCondition(conditionBuilder.orCondition(conditions));
if (conditions.length == 0) {
// no roles/groups to fetch based on the pre-filters applied to the memberOf values
return List.of();
}
ldapQuery.addWhereCondition(conditionBuilder.orCondition(conditions));
return LDAPUtils.loadAllLDAPObjects(ldapQuery, ldapConfig);
}
}

View File

@ -599,6 +599,89 @@ public class LDAPGroupMapperTest extends AbstractLDAPTest {
testRealm().components().component(groupMapperRep.getId()).update(groupMapperRep);
}
@Test
public void test05_DoNotResolveGroupsIfMemberOfNotChildOfGroupBaseDN() {
ComponentRepresentation groupMapperRep = findMapperRepByName("groupsMapper");
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
RealmModel appRealm = ctx.getRealm();
LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "streetMapper", "street", LDAPConstants.STREET);
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ctx.getLdapProvider(), appRealm);
String baseGroupDN = groupMapper.getConfig().getLDAPGroupsDn();
String invalidBaseGroupDN = baseGroupDN.replace("ou=Groups", "ou=OtherGroupBaseDN");
LDAPObject carlos = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "alice", "Alice", "Jack", "alice@email.org", "cn=mygroup," + invalidBaseGroupDN, "1234");
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), carlos, "Password1");
LDAPTestUtils.updateConfigOptions(mapperModel,
GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE,
GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE, LDAPConstants.STREET);
appRealm.updateComponent(mapperModel);
});
ComponentRepresentation streetMapperRep = findMapperRepByName("streetMapper");
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
RealmModel appRealm = ctx.getRealm();
UserModel user = session.users().getUserByUsername(appRealm, "alice");
Set<GroupModel> groups = user.getGroupsStream().collect(Collectors.toSet());
Assert.assertTrue(groups.isEmpty());
});
testRealm().components().component(streetMapperRep.getId()).remove();
groupMapperRep.getConfig().putSingle(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE);
testRealm().components().component(groupMapperRep.getId()).update(groupMapperRep);
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
LDAPTestUtils.removeLDAPUserByUsername(ctx.getLdapProvider(), ctx.getRealm(), ldapFedProvider.getLdapIdentityStore().getConfig(), "alice");
});
}
@Test
public void test05_DoNotResolveGroupsIfMemberOfHasEmptyValue() {
ComponentRepresentation groupMapperRep = findMapperRepByName("groupsMapper");
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
RealmModel appRealm = ctx.getRealm();
LDAPTestUtils.addUserAttributeMapper(appRealm, ctx.getLdapModel(), "streetMapper", "street", LDAPConstants.STREET);
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
LDAPObject carlos = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "alice", "Alice", "Jack", "alice@email.org", "", "1234");
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), carlos, "Password1");
LDAPTestUtils.updateConfigOptions(mapperModel,
GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE,
GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE, LDAPConstants.STREET);
appRealm.updateComponent(mapperModel);
});
ComponentRepresentation streetMapperRep = findMapperRepByName("streetMapper");
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
RealmModel appRealm = ctx.getRealm();
UserModel user = session.users().getUserByUsername(appRealm, "alice");
Set<GroupModel> groups = user.getGroupsStream().collect(Collectors.toSet());
Assert.assertTrue(groups.isEmpty());
});
testRealm().components().component(streetMapperRep.getId()).remove();
groupMapperRep.getConfig().putSingle(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE);
testRealm().components().component(groupMapperRep.getId()).update(groupMapperRep);
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
LDAPTestUtils.removeLDAPUserByUsername(ctx.getLdapProvider(), ctx.getRealm(), ldapFedProvider.getLdapIdentityStore().getConfig(), "alice");
});
}
// KEYCLOAK-5017
@Test