KEYCLOAK-2227 Added UserRolesRetrieveStrategy. Possibility to read user role mappings through 'memberOf' attribute

This commit is contained in:
mposolda 2015-12-14 23:21:11 +01:00
parent 215d59b1e5
commit 358c273d39
6 changed files with 191 additions and 12 deletions

View file

@ -62,6 +62,14 @@ public class LDAPDn {
return firstEntry.attrName;
}
/**
* @return string attribute value like "joe" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdnAttrValue() {
Entry firstEntry = entries.getFirst();
return firstEntry.attrValue;
}
/**
*
* @return string like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org"
@ -72,6 +80,21 @@ public class LDAPDn {
return toString(parentDnEntries);
}
public boolean isDescendantOf(LDAPDn expectedParentDn) {
int parentEntriesCount = expectedParentDn.entries.size();
Deque<Entry> myEntries = new LinkedList<>(this.entries);
boolean someRemoved = false;
while (myEntries.size() > parentEntriesCount) {
myEntries.removeFirst();
someRemoved = true;
}
String myEntriesParentStr = toString(myEntries).toLowerCase();
String expectedParentDnStr = expectedParentDn.toString().toLowerCase();
return someRemoved && myEntriesParentStr.equals(expectedParentDnStr);
}
public void addFirst(String rdnName, String rdnValue) {
rdnValue = escape(rdnValue);
entries.addFirst(new Entry(rdnName, rdnValue));

View file

@ -56,6 +56,9 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
// See docs for Mode enum
public static final String MODE = "mode";
// See docs for UserRolesRetriever enum
public static final String USER_ROLES_RETRIEVE_STRATEGY = "user.roles.retrieve.strategy";
// Customized LDAP filter which is added to the whole LDAP query
public static final String ROLES_LDAP_FILTER = "roles.ldap.filter";
@ -184,10 +187,10 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
protected MembershipType getMembershipTypeLdapAttribute(UserFederationMapperModel mapperModel) {
String membershipType = mapperModel.getConfig().get(MEMBERSHIP_ATTRIBUTE_TYPE);
return membershipType!=null ? Enum.valueOf(MembershipType.class, membershipType) : MembershipType.DN;
return (membershipType!=null && !membershipType.isEmpty()) ? Enum.valueOf(MembershipType.class, membershipType) : MembershipType.DN;
}
private String getMembershipFromUser(LDAPObject ldapUser, MembershipType membershipType) {
protected String getMembershipFromUser(LDAPObject ldapUser, MembershipType membershipType) {
return membershipType == MembershipType.DN ? ldapUser.getDn().toString() : ldapUser.getAttributeAsString(ldapUser.getRdnAttributeName());
}
@ -218,6 +221,11 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
return Enum.valueOf(Mode.class, modeString.toUpperCase());
}
private UserRolesRetrieveStrategy getUserRolesRetrieveStrategy(UserFederationMapperModel mapperModel) {
String strategyString = mapperModel.getConfig().get(USER_ROLES_RETRIEVE_STRATEGY);
return (strategyString!=null && !strategyString.isEmpty()) ? Enum.valueOf(UserRolesRetrieveStrategy.class, strategyString) : UserRolesRetrieveStrategy.LOAD_ROLES_BY_MEMBER_ATTRIBUTE;
}
public LDAPObject createLDAPRole(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider) {
LDAPObject ldapObject = new LDAPObject();
String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
@ -296,14 +304,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
}
protected List<LDAPObject> getLDAPRoleMappings(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
String membershipAttr = getMembershipLdapAttribute(mapperModel);
String userMembership = getMembershipFromUser(ldapUser, getMembershipTypeLdapAttribute(mapperModel));
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(membershipAttr, userMembership);
ldapQuery.addWhereCondition(membershipCondition);
return ldapQuery.getResultList();
UserRolesRetrieveStrategy strategy = getUserRolesRetrieveStrategy(mapperModel);
return strategy.getLDAPRoleMappings(this, mapperModel, ldapProvider, ldapUser);
}
@Override
@ -320,6 +322,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
@Override
public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
UserRolesRetrieveStrategy strategy = getUserRolesRetrieveStrategy(mapperModel);
strategy.beforeUserLDAPQuery(mapperModel, query);
}

View file

@ -52,22 +52,25 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
ProviderConfigProperty.STRING_TYPE, LDAPConstants.MEMBER);
configProperties.add(membershipLDAPAttribute);
List<String> membershipTypes = new LinkedList<>();
for (RoleLDAPFederationMapper.MembershipType membershipType : RoleLDAPFederationMapper.MembershipType.values()) {
membershipTypes.add(membershipType.toString());
}
ProviderConfigProperty membershipType = createConfigProperty(RoleLDAPFederationMapper.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
"DN means that LDAP role has it's members declared in form of their full DN. For example ( 'member: uid=john,ou=users,dc=example,dc=com' . " +
"UID means that LDAP role has it's members declared in form of pure user uids. For example ( 'memberuid: john' ))",
"UID means that LDAP role has it's members declared in form of pure user uids. For example ( 'memberUid: john' ))",
ProviderConfigProperty.LIST_TYPE, membershipTypes);
configProperties.add(membershipType);
ProviderConfigProperty ldapFilter = createConfigProperty(RoleLDAPFederationMapper.ROLES_LDAP_FILTER,
"LDAP Filter",
"LDAP Filter adds additional custom filter to the whole query. Make sure that it starts with '(' and ends with ')'",
ProviderConfigProperty.STRING_TYPE, null);
configProperties.add(ldapFilter);
List<String> modes = new LinkedList<>();
for (RoleLDAPFederationMapper.Mode mode : RoleLDAPFederationMapper.Mode.values()) {
modes.add(mode.toString());
@ -79,6 +82,20 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
ProviderConfigProperty.LIST_TYPE, modes);
configProperties.add(mode);
List<String> roleRetrievers = new LinkedList<>();
for (UserRolesRetrieveStrategy retriever : UserRolesRetrieveStrategy.values()) {
roleRetrievers.add(retriever.toString());
}
ProviderConfigProperty retriever = createConfigProperty(RoleLDAPFederationMapper.USER_ROLES_RETRIEVE_STRATEGY, "User Roles Retrieve Strategy",
"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. " +
"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 extension."
,
ProviderConfigProperty.LIST_TYPE, roleRetrievers);
configProperties.add(retriever);
ProviderConfigProperty useRealmRolesMappings = createConfigProperty(RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "Use Realm Roles Mapping",
"If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings", ProviderConfigProperty.BOOLEAN_TYPE, "true");
configProperties.add(useRealmRolesMappings);

View file

@ -0,0 +1,124 @@
package org.keycloak.federation.ldap.mappers;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPDn;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.UserFederationMapperModel;
/**
* Strategy for how to retrieve LDAP roles of user
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public enum UserRolesRetrieveStrategy {
/**
* Roles of user will be retrieved by sending LDAP query to retrieve all roles where "member" is our user
*/
LOAD_ROLES_BY_MEMBER_ATTRIBUTE {
@Override
public List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
LDAPQuery ldapQuery = roleMapper.createRoleQuery(mapperModel, ldapProvider);
String membershipAttr = roleMapper.getMembershipLdapAttribute(mapperModel);
String userMembership = roleMapper.getMembershipFromUser(ldapUser, roleMapper.getMembershipTypeLdapAttribute(mapperModel));
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(membershipAttr, userMembership);
ldapQuery.addWhereCondition(membershipCondition);
return ldapQuery.getResultList();
}
@Override
public void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
}
},
/**
* Roles of user will be retrieved from "memberOf" attribute of our user
*/
GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE {
@Override
public List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
Set<String> memberOfValues = ldapUser.getAttributeAsSet(LDAPConstants.MEMBER_OF);
if (memberOfValues == null) {
return Collections.emptyList();
}
List<LDAPObject> roles = new LinkedList<>();
LDAPDn parentDn = LDAPDn.fromString(roleMapper.getRolesDn(mapperModel));
for (String roleDn : memberOfValues) {
LDAPDn roleDN = LDAPDn.fromString(roleDn);
if (roleDN.isDescendantOf(parentDn)) {
LDAPObject role = new LDAPObject();
role.setDn(roleDN);
String firstDN = roleDN.getFirstRdnAttrName();
if (firstDN.equalsIgnoreCase(roleMapper.getRoleNameLdapAttribute(mapperModel))) {
role.setRdnAttributeName(firstDN);
role.setSingleAttribute(firstDN, roleDN.getFirstRdnAttrValue());
roles.add(role);
}
}
}
return roles;
}
@Override
public void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
query.addReturningLdapAttribute(LDAPConstants.MEMBER_OF);
query.addReturningReadOnlyLdapAttribute(LDAPConstants.MEMBER_OF);
}
},
/**
* Extension specific to Active Directory. Roles of user will be retrieved by sending LDAP query to retrieve all roles where "member" is our user.
* The query will be able to retrieve memberships recursively
* (Assume "role1" has member "role2" and role2 has member "johnuser". Then searching for roles of "johnuser" will return both "role1" and "role2" )
*
* This is using AD specific extension LDAP_MATCHING_RULE_IN_CHAIN, so likely doesn't work on other LDAP servers
*/
LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY {
@Override
public List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser) {
LDAPQuery ldapQuery = roleMapper.createRoleQuery(mapperModel, ldapProvider);
String membershipAttr = roleMapper.getMembershipLdapAttribute(mapperModel);
membershipAttr = membershipAttr + LDAPConstants.LDAP_MATCHING_RULE_IN_CHAIN;
String userMembership = roleMapper.getMembershipFromUser(ldapUser, roleMapper.getMembershipTypeLdapAttribute(mapperModel));
Condition membershipCondition = new LDAPQueryConditionsBuilder().equal(membershipAttr, userMembership);
ldapQuery.addWhereCondition(membershipCondition);
return ldapQuery.getResultList();
}
@Override
public void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query) {
}
};
public abstract List<LDAPObject> getLDAPRoleMappings(RoleLDAPFederationMapper roleMapper, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser);
public abstract void beforeUserLDAPQuery(UserFederationMapperModel mapperModel, LDAPQuery query);
}

View file

@ -18,5 +18,14 @@ public class LDAPDnTest {
Assert.assertEquals("uid=Johny\\,Depp,ou=People,dc=keycloak,dc=org", dn.toString());
Assert.assertEquals("ou=People,dc=keycloak,dc=org", dn.getParentDn());
Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=keycloak, dc=org")));
Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("dc=org")));
Assert.assertTrue(dn.isDescendantOf(LDAPDn.fromString("DC=keycloak, DC=org")));
Assert.assertFalse(dn.isDescendantOf(LDAPDn.fromString("dc=keycloakk, dc=org")));
Assert.assertFalse(dn.isDescendantOf(dn));
Assert.assertEquals("uid", dn.getFirstRdnAttrName());
Assert.assertEquals("Johny\\,Depp", dn.getFirstRdnAttrValue());
}
}

View file

@ -87,6 +87,8 @@ public class LDAPConstants {
public static final String CREATE_TIMESTAMP = "createTimestamp";
public static final String MODIFY_TIMESTAMP = "modifyTimestamp";
public static final String LDAP_MATCHING_RULE_IN_CHAIN = ":1.2.840.113556.1.4.1941:";
public static String getUuidAttributeName(String vendor) {
if (vendor != null) {
switch (vendor) {