KEYCLOAK-2227 Added UserRolesRetrieveStrategy. Possibility to read user role mappings through 'memberOf' attribute
This commit is contained in:
parent
215d59b1e5
commit
358c273d39
6 changed files with 191 additions and 12 deletions
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue