Merge pull request #4691 from mposolda/master

KEYCLOAK-5848 Possibility to configure different attribute for GET_GR…
This commit is contained in:
Marek Posolda 2017-11-14 16:09:26 +01:00 committed by GitHub
commit 913c94dbd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 173 additions and 21 deletions

View file

@ -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) {
}

View file

@ -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()) {

View file

@ -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);
}
};

View file

@ -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

View file

@ -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. " +

View file

@ -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);
}

View file

@ -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")

View file

@ -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);

View file

@ -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);
}
}
}