keycloak-aplcache
Changes
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java 24(+14 -10)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java 21(+19 -2)
Details
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
index a7cf098..be5e6b9 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/model/LDAPDn.java
@@ -63,6 +63,14 @@ public class LDAPDn {
}
/**
+ * @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));
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
index a62cc5e..7eac273 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
@@ -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);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
index b244d22..5cc2997 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
@@ -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);
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserRolesRetrieveStrategy.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserRolesRetrieveStrategy.java
new file mode 100644
index 0000000..dd7cd31
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserRolesRetrieveStrategy.java
@@ -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);
+
+}
diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
index 77bc4ce..5631573 100644
--- a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
+++ b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPDnTest.java
@@ -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());
}
}
diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index 79bd7b5..2d403f3 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -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) {