mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
LDAP group mapper skips configured filter and imports all groups with memberOf strategy when fetching the user's groups
Closes #37537 Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
parent
f45b8e0c6d
commit
6bf5727b7b
@ -31,9 +31,7 @@ import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.utils.StreamsUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -90,39 +88,35 @@ public interface UserRolesRetrieveStrategy {
|
||||
};
|
||||
|
||||
/**
|
||||
* Roles of user will be retrieved from "memberOf" attribute of our user
|
||||
* Roles of user will be loaded from LDAP based on "memberOf" attribute of our user
|
||||
*/
|
||||
class GetRolesFromUserMemberOfAttribute implements UserRolesRetrieveStrategy {
|
||||
|
||||
@Override
|
||||
public List<LDAPObject> getLDAPRoleMappings(CommonLDAPGroupMapper roleOrGroupMapper, LDAPObject ldapUser, LDAPConfig ldapConfig) {
|
||||
String memberOfLdapAttrName = roleOrGroupMapper.getConfig().getMemberOfLdapAttribute();
|
||||
|
||||
Set<String> memberOfValues = ldapUser.getAttributeAsSet(memberOfLdapAttrName);
|
||||
if (memberOfValues == null) {
|
||||
Set<String> memberOfValues = ldapUser.getAttributeAsSet(roleOrGroupMapper.getConfig().getMemberOfLdapAttribute());
|
||||
if (memberOfValues == null || memberOfValues.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<LDAPObject> roles = new LinkedList<>();
|
||||
LDAPDn parentDn = LDAPDn.fromString(roleOrGroupMapper.getConfig().getLDAPGroupsDn());
|
||||
try (LDAPQuery ldapQuery = roleOrGroupMapper.createLDAPGroupQuery()) {
|
||||
|
||||
for (String roleDn : memberOfValues) {
|
||||
LDAPDn roleDN = LDAPDn.fromString(roleDn);
|
||||
if (roleDN.isDescendantOf(parentDn)) {
|
||||
LDAPObject role = new LDAPObject();
|
||||
role.setDn(roleDN);
|
||||
String rdnAttr = roleOrGroupMapper.getConfig().getLDAPGroupNameLdapAttribute();
|
||||
LDAPQueryConditionsBuilder conditionBuilder = new LDAPQueryConditionsBuilder();
|
||||
|
||||
LDAPDn.RDN firstRDN = roleDN.getFirstRdn();
|
||||
String attrKey = roleOrGroupMapper.getConfig().getLDAPGroupNameLdapAttribute();
|
||||
String attrVal = firstRDN.getAttrValue(attrKey);
|
||||
if (attrVal != null) {
|
||||
role.setRdnAttributeName(attrKey);
|
||||
role.setSingleAttribute(attrKey, attrVal);
|
||||
roles.add(role);
|
||||
}
|
||||
}
|
||||
// load only those groups/roles the user is memberOf
|
||||
// we do this by query to apply defined custom filters
|
||||
ldapQuery.addWhereCondition(conditionBuilder.orCondition(
|
||||
memberOfValues.stream()
|
||||
.map(LDAPDn::fromString)
|
||||
.filter(roleDN -> roleDN.isDescendantOf(LDAPDn.fromString(roleOrGroupMapper.getConfig().getLDAPGroupsDn())))
|
||||
.map(roleDN -> conditionBuilder.equal(rdnAttr, roleDN.getFirstRdn().getAttrValue(rdnAttr)))
|
||||
.toArray(Condition[]::new)
|
||||
)
|
||||
);
|
||||
|
||||
return LDAPUtils.loadAllLDAPObjects(ldapQuery, ldapConfig);
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,6 +144,7 @@ public interface UserRolesRetrieveStrategy {
|
||||
*/
|
||||
class LoadRolesByMemberRecursively extends LoadRolesByMember {
|
||||
|
||||
@Override
|
||||
protected Condition getMembershipCondition(String membershipAttr, String userMembership) {
|
||||
return new LDAPQueryConditionsBuilder().equal(membershipAttr + LDAPConstants.LDAP_MATCHING_RULE_IN_CHAIN, userMembership);
|
||||
}
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2025 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.testsuite.federation.ldap;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.ldap.LDAPUtils;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||
import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
|
||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
|
||||
import org.keycloak.storage.user.SynchronizationResult;
|
||||
import org.keycloak.testsuite.util.LDAPRule;
|
||||
import org.keycloak.testsuite.util.LDAPTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LDAPGroupMapperCustomMemberOfTest extends AbstractLDAPTest {
|
||||
|
||||
@ClassRule
|
||||
public static LDAPRule ldapRule = new LDAPRule();
|
||||
|
||||
@Override
|
||||
protected LDAPRule getLDAPRule() {
|
||||
return ldapRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterImportTestRealm() {
|
||||
}
|
||||
|
||||
// Test GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE with custom 'Member-Of LDAP Attribute'. As a workaround, we are testing this with custom attribute "street"
|
||||
// just because it's available on all the LDAP servers
|
||||
@Test
|
||||
public void getGroupsFromUserMemberOfStrategyWithFilterTest() throws Exception {
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
// Create group mapper
|
||||
String descriptionAttrName = LDAPTestUtils.getGroupDescriptionLDAPAttrName(ctx.getLdapProvider());
|
||||
LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ctx.getLdapModel(), LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName,
|
||||
GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE,
|
||||
GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE, LDAPConstants.STREET,
|
||||
GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false",
|
||||
GroupMapperConfig.GROUPS_LDAP_FILTER, "(cn=ldap-group2)");
|
||||
|
||||
LDAPObject group1 = LDAPTestUtils.createLDAPGroup(session, appRealm, ctx.getLdapModel(), "ldap-group1", descriptionAttrName, "group1 - description");
|
||||
LDAPObject group2 = LDAPTestUtils.createLDAPGroup(session, appRealm, ctx.getLdapModel(), "ldap-group2", descriptionAttrName, "group2 - description");
|
||||
LDAPObject group3 = LDAPTestUtils.createLDAPGroup(session, appRealm, ctx.getLdapModel(), "ldap-group3", descriptionAttrName, "group3 - description");
|
||||
|
||||
// Create new user in LDAP. Add him some "street" referencing existing LDAP Group
|
||||
LDAPObject carlos = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "carloskeycloak", "Carlos", "Doel", "carlos.doel@email.org", "street", "1234");
|
||||
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), carlos, "Password1");
|
||||
|
||||
// add "memberOf" attributes (simulated by "street" attr) to Carlos
|
||||
LDAPUtils.addMember(LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel()), MembershipType.DN, LDAPConstants.STREET, "not-used", carlos, group1);
|
||||
LDAPUtils.addMember(LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel()), MembershipType.DN, LDAPConstants.STREET, "not-used", carlos, group2);
|
||||
LDAPUtils.addMember(LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel()), MembershipType.DN, LDAPConstants.STREET, "not-used", carlos, group3);
|
||||
|
||||
// Sync LDAP groups to Keycloak DB - it should bring only group2 due to the defined filter
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
|
||||
SynchronizationResult syncDataResult = new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(appRealm);
|
||||
assertThat(syncDataResult.getAdded(), equalTo(1));
|
||||
});
|
||||
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
// Get user in Keycloak. It should not load any new groups where Carlos is memberOf due to defined filter
|
||||
UserModel carlos = session.users().getUserByUsername(appRealm, "carloskeycloak");
|
||||
List<String> carlosGroups = carlos.getGroupsStream().map(GroupModel::getName).collect(Collectors.toList());
|
||||
|
||||
assertThat(carlosGroups, hasSize(1));
|
||||
assertThat(carlosGroups, hasItem("ldap-group2"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user