Merge pull request #4691 from mposolda/master
KEYCLOAK-5848 Possibility to configure different attribute for GET_GR…
This commit is contained in:
commit
913c94dbd1
9 changed files with 173 additions and 21 deletions
|
@ -34,16 +34,6 @@ import java.util.Map;
|
|||
*/
|
||||
public abstract class AbstractLDAPStorageMapperFactory implements LDAPStorageMapperFactory<LDAPStorageMapper> {
|
||||
|
||||
// Used to map attributes from LDAP to UserModel attributes
|
||||
public static final String ATTRIBUTE_MAPPER_CATEGORY = "Attribute Mapper";
|
||||
|
||||
// Used to map roles from LDAP to UserModel users
|
||||
public static final String ROLE_MAPPER_CATEGORY = "Role Mapper";
|
||||
|
||||
|
||||
// Used to map group from LDAP to UserModel users
|
||||
public static final String GROUP_MAPPER_CATEGORY = "Group Mapper";
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
|
|
@ -42,9 +42,13 @@ public abstract class CommonLDAPGroupMapperConfig {
|
|||
// See docs for Mode enum
|
||||
public static final String MODE = "mode";
|
||||
|
||||
// See docs for UserRolesRetriever enum
|
||||
// See docs for UserRolesRetrieveStrategy enum
|
||||
public static final String USER_ROLES_RETRIEVE_STRATEGY = "user.roles.retrieve.strategy";
|
||||
|
||||
// Used just for UserRolesRetrieveStrategy.GetRolesFromUserMemberOfAttribute. It's the name of the attribute on LDAP user, which is used to track the groups which user is member.
|
||||
// Usually it will "memberof"
|
||||
public static final String MEMBEROF_LDAP_ATTRIBUTE = "memberof.ldap.attribute";
|
||||
|
||||
|
||||
protected final ComponentModel mapperModel;
|
||||
|
||||
|
@ -67,6 +71,11 @@ public abstract class CommonLDAPGroupMapperConfig {
|
|||
return membershipUserAttrName!=null ? membershipUserAttrName : ldapConfig.getUsernameLdapAttribute();
|
||||
}
|
||||
|
||||
public String getMemberOfLdapAttribute() {
|
||||
String memberOfLdapAttrName = mapperModel.getConfig().getFirst(MEMBEROF_LDAP_ATTRIBUTE);
|
||||
return memberOfLdapAttrName!=null ? memberOfLdapAttrName : LDAPConstants.MEMBER_OF;
|
||||
}
|
||||
|
||||
public LDAPGroupMapperMode getMode() {
|
||||
String modeString = mapperModel.getConfig().getFirst(MODE);
|
||||
if (modeString == null || modeString.isEmpty()) {
|
||||
|
|
|
@ -42,7 +42,7 @@ public interface UserRolesRetrieveStrategy {
|
|||
|
||||
List<LDAPObject> getLDAPRoleMappings(CommonLDAPGroupMapper roleOrGroupMapper, LDAPObject ldapUser, LDAPConfig ldapConfig);
|
||||
|
||||
void beforeUserLDAPQuery(LDAPQuery query);
|
||||
void beforeUserLDAPQuery(CommonLDAPGroupMapper roleOrGroupMapper, LDAPQuery query);
|
||||
|
||||
|
||||
// Impl subclasses
|
||||
|
@ -66,7 +66,7 @@ public interface UserRolesRetrieveStrategy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void beforeUserLDAPQuery(LDAPQuery query) {
|
||||
public void beforeUserLDAPQuery(CommonLDAPGroupMapper roleOrGroupMapper, LDAPQuery query) {
|
||||
}
|
||||
|
||||
protected Condition getMembershipCondition(String membershipAttr, String userMembership) {
|
||||
|
@ -82,7 +82,9 @@ public interface UserRolesRetrieveStrategy {
|
|||
|
||||
@Override
|
||||
public List<LDAPObject> getLDAPRoleMappings(CommonLDAPGroupMapper roleOrGroupMapper, LDAPObject ldapUser, LDAPConfig ldapConfig) {
|
||||
Set<String> memberOfValues = ldapUser.getAttributeAsSet(LDAPConstants.MEMBER_OF);
|
||||
String memberOfLdapAttrName = roleOrGroupMapper.getConfig().getMemberOfLdapAttribute();
|
||||
|
||||
Set<String> memberOfValues = ldapUser.getAttributeAsSet(memberOfLdapAttrName);
|
||||
if (memberOfValues == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -108,9 +110,11 @@ public interface UserRolesRetrieveStrategy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void beforeUserLDAPQuery(LDAPQuery query) {
|
||||
query.addReturningLdapAttribute(LDAPConstants.MEMBER_OF);
|
||||
query.addReturningReadOnlyLdapAttribute(LDAPConstants.MEMBER_OF);
|
||||
public void beforeUserLDAPQuery(CommonLDAPGroupMapper roleOrGroupMapper, LDAPQuery query) {
|
||||
String memberOfLdapAttrName = roleOrGroupMapper.getConfig().getMemberOfLdapAttribute();
|
||||
|
||||
query.addReturningLdapAttribute(memberOfLdapAttrName);
|
||||
query.addReturningReadOnlyLdapAttribute(memberOfLdapAttrName);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -522,7 +522,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
|||
public void beforeLDAPQuery(LDAPQuery query) {
|
||||
String strategyKey = config.getUserGroupsRetrieveStrategy();
|
||||
UserRolesRetrieveStrategy strategy = factory.getUserGroupsRetrieveStrategy(strategyKey);
|
||||
strategy.beforeUserLDAPQuery(query);
|
||||
strategy.beforeUserLDAPQuery(this, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -173,12 +173,20 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
|
|||
config.property().name(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY)
|
||||
.label("User Groups Retrieve Strategy")
|
||||
.helpText("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. " +
|
||||
"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' . " +
|
||||
"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.")
|
||||
.type(ProviderConfigProperty.LIST_TYPE)
|
||||
.options(ROLE_RETRIEVERS)
|
||||
.defaultValue(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE)
|
||||
.add()
|
||||
.property().name(GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE)
|
||||
.label("Member-Of LDAP Attribute")
|
||||
.helpText("Used just when 'User Roles Retrieve Strategy' is GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE . " +
|
||||
"It specifies the name of the LDAP attribute on the LDAP user, which contains the groups, which the user is member of. " +
|
||||
"Usually it will be 'memberOf' and that's also the default value.")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue(LDAPConstants.MEMBER_OF)
|
||||
.add()
|
||||
.property().name(GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES)
|
||||
.label("Mapped Group Attributes")
|
||||
.helpText("List of names of attributes divided by comma. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. " +
|
||||
|
|
|
@ -294,7 +294,7 @@ public class RoleLDAPStorageMapper extends AbstractLDAPStorageMapper implements
|
|||
public void beforeLDAPQuery(LDAPQuery query) {
|
||||
String strategyKey = config.getUserRolesRetrieveStrategy();
|
||||
UserRolesRetrieveStrategy strategy = factory.getUserRolesRetrieveStrategy(strategyKey);
|
||||
strategy.beforeUserLDAPQuery(query);
|
||||
strategy.beforeUserLDAPQuery(this, query);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapperFactory;
|
|||
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
|
||||
import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
||||
import org.keycloak.storage.ldap.mappers.membership.UserRolesRetrieveStrategy;
|
||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -160,12 +161,20 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
|
|||
config.property().name(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY)
|
||||
.label("User Roles Retrieve Strategy")
|
||||
.helpText("Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. " +
|
||||
"GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. " +
|
||||
"GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. Or from the other attribute specified by 'Member-Of LDAP Attribute' . " +
|
||||
"LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.")
|
||||
.type(ProviderConfigProperty.LIST_TYPE)
|
||||
.options(roleRetrievers)
|
||||
.defaultValue(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE)
|
||||
.add()
|
||||
.property().name(GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE)
|
||||
.label("Member-Of LDAP Attribute")
|
||||
.helpText("Used just when 'User Roles Retrieve Strategy' is GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE . " +
|
||||
"It specifies the name of the LDAP attribute on the LDAP user, which contains the roles (LDAP Groups), which the user is member of. " +
|
||||
"Usually it will be 'memberOf' and that's also the default value.")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue(LDAPConstants.MEMBER_OF)
|
||||
.add()
|
||||
.property().name(RoleMapperConfig.USE_REALM_ROLES_MAPPING)
|
||||
.label("Use Realm Roles Mapping")
|
||||
.helpText("If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings")
|
||||
|
|
|
@ -384,6 +384,72 @@ public class LDAPGroupMapperTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// KEYCLOAK-5848
|
||||
// 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 test05_getGroupsFromUserMemberOfStrategyTest() throws Exception {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
MultivaluedHashMap<String, String> oldGroupMapperCfg;
|
||||
try {
|
||||
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
|
||||
// Create street attribute mapper
|
||||
LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
|
||||
|
||||
// Find DN of "group1"
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "groupsMapper");
|
||||
oldGroupMapperCfg = new MultivaluedHashMap<>(mapperModel.getConfig());
|
||||
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
|
||||
LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName("group1");
|
||||
String ldapGroupDN = ldapGroup.getDn().toString();
|
||||
|
||||
// Create new user in LDAP. Add him some "street" referencing existing LDAP Group
|
||||
LDAPObject carlos = LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "carloskeycloak", "Carlos", "Doel", "carlos.doel@email.org", ldapGroupDN, "1234");
|
||||
LDAPTestUtils.updateLDAPPassword(ldapProvider, carlos, "Password1");
|
||||
|
||||
// Update group mapper
|
||||
LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel,
|
||||
GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, GroupMapperConfig.GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE,
|
||||
GroupMapperConfig.MEMBEROF_LDAP_ATTRIBUTE, LDAPConstants.STREET);
|
||||
appRealm.updateComponent(mapperModel);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
|
||||
// Get user in Keycloak. Ensure that he is member of requested group
|
||||
UserModel carlos = session.users().getUserByUsername("carloskeycloak", appRealm);
|
||||
Set<GroupModel> carlosGroups = carlos.getGroups();
|
||||
|
||||
GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
|
||||
GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
|
||||
GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
|
||||
|
||||
Assert.assertTrue(carlosGroups.contains(group1));
|
||||
Assert.assertFalse(carlosGroups.contains(group11));
|
||||
Assert.assertFalse(carlosGroups.contains(group12));
|
||||
|
||||
Assert.assertEquals(1, carlosGroups.size());
|
||||
|
||||
// Revert mappers
|
||||
ComponentModel streetMapper = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "streetMapper");
|
||||
appRealm.removeComponent(streetMapper);
|
||||
|
||||
ComponentModel groupMapper = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "groupsMapper");
|
||||
groupMapper.setConfig(oldGroupMapperCfg);
|
||||
appRealm.updateComponent(groupMapper);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void deleteGroupMappingsInLDAP(GroupLDAPStorageMapper groupMapper, LDAPObject ldapUser, String groupName) {
|
||||
LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName(groupName);
|
||||
groupMapper.deleteGroupMappingInLDAP(ldapUser, ldapGroup);
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.junit.rules.TestRule;
|
|||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.services.managers.UserStorageSyncManager;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
@ -44,7 +46,10 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
|
||||
import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapper;
|
||||
import org.keycloak.storage.ldap.mappers.membership.role.RoleMapperConfig;
|
||||
import org.keycloak.storage.user.SynchronizationResult;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
|
@ -462,4 +467,65 @@ public class LDAPRoleMappingsTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
// KEYCLOAK-5848
|
||||
// Test GET_ROLES_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 test05_getRolesFromUserMemberOfStrategyTest() throws Exception {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
MultivaluedHashMap<String, String> oldRoleMapperCfg;
|
||||
try {
|
||||
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
|
||||
// Create street attribute mapper
|
||||
LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
|
||||
|
||||
// Find DN of "group1"
|
||||
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "realmRolesMapper");
|
||||
oldRoleMapperCfg = new MultivaluedHashMap<>(mapperModel.getConfig());
|
||||
RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(mapperModel, ldapProvider, appRealm);
|
||||
LDAPObject ldapRole = roleMapper.loadLDAPRoleByName("realmRole1");
|
||||
String ldapRoleDN = ldapRole.getDn().toString();
|
||||
|
||||
// Create new user in LDAP. Add him some "street" referencing existing LDAP Group
|
||||
LDAPObject carlos = LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "carloskeycloak", "Carlos", "Doel", "carlos.doel@email.org", ldapRoleDN, "1234");
|
||||
LDAPTestUtils.updateLDAPPassword(ldapProvider, carlos, "Password1");
|
||||
|
||||
// Update group mapper
|
||||
LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel,
|
||||
RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY, RoleMapperConfig.GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE,
|
||||
RoleMapperConfig.MEMBEROF_LDAP_ATTRIBUTE, LDAPConstants.STREET);
|
||||
appRealm.updateComponent(mapperModel);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
|
||||
// Get user in Keycloak. Ensure that he is member of requested group
|
||||
UserModel carlos = session.users().getUserByUsername("carloskeycloak", appRealm);
|
||||
Set<RoleModel> carlosRoles = carlos.getRealmRoleMappings();
|
||||
|
||||
RoleModel realmRole1 = appRealm.getRole("realmRole1");
|
||||
RoleModel realmRole2 = appRealm.getRole("realmRole2");
|
||||
|
||||
Assert.assertTrue(carlosRoles.contains(realmRole1));
|
||||
Assert.assertFalse(carlosRoles.contains(realmRole2));
|
||||
|
||||
// Revert mappers
|
||||
ComponentModel streetMapper = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "streetMapper");
|
||||
appRealm.removeComponent(streetMapper);
|
||||
|
||||
ComponentModel roleMapper = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "realmRolesMapper");
|
||||
roleMapper.setConfig(oldRoleMapperCfg);
|
||||
appRealm.updateComponent(roleMapper);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue