keycloak-aplcache

KEYCLOAK-2227 Added UserRolesRetrieveStrategy. Possibility

12/14/2015 8:21:11 PM

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