keycloak-aplcache

Details

diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java
index f51bdad..1d62a3c 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPIdentityQuery.java
@@ -186,7 +186,7 @@ public class LDAPIdentityQuery {
     }
 
     public Set<Condition> getConditions() {
-        return unmodifiableSet(this.conditions);
+        return this.conditions;
     }
 
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index 58364d3..1e9918d 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -9,7 +9,6 @@ import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserFederationMapper;
 import org.keycloak.models.UserFederationMapperModel;
 import org.keycloak.models.UserModel;
 
@@ -179,4 +178,9 @@ public class LDAPUtils {
         String usernameAttr = config.getUsernameLdapAttribute();
         return (String) ldapUser.getAttribute(usernameAttr);
     }
+
+    public static boolean parseBooleanParameter(UserFederationMapperModel mapperModel, String paramName) {
+        String readOnly = mapperModel.getConfig().get(paramName);
+        return Boolean.parseBoolean(readOnly);
+    }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
new file mode 100644
index 0000000..483d825
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
@@ -0,0 +1,174 @@
+package org.keycloak.federation.ldap.mappers;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
+import org.keycloak.federation.ldap.idm.model.LDAPObject;
+import org.keycloak.federation.ldap.idm.query.Condition;
+import org.keycloak.federation.ldap.idm.query.QueryParameter;
+import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
+import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
+
+/**
+ * Mapper useful for the LDAP deployments when some attribute (usually CN) is mapped to full name of user
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
+
+    public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
+    public static final String READ_ONLY = "read.only";
+
+    @Override
+    public String getHelpText() {
+        return "Some help text - full name mapper - TODO";
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return null;
+    }
+
+    @Override
+    public String getId() {
+        return "full-name-ldap-mapper";
+    }
+
+    @Override
+    public void importUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel user, boolean isCreate) {
+        String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+        String fullName = (String) ldapObject.getAttribute(ldapFullNameAttrName);
+        fullName = fullName.trim();
+        if (fullName != null) {
+            int lastSpaceIndex = fullName.lastIndexOf(" ");
+            if (lastSpaceIndex == -1) {
+                user.setLastName(fullName);
+            } else {
+                user.setFirstName(fullName.substring(0, lastSpaceIndex));
+                user.setLastName(fullName.substring(lastSpaceIndex + 1));
+            }
+        }
+    }
+
+    @Override
+    public void registerUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel localUser) {
+        String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+        String fullName = getFullName(localUser.getFirstName(), localUser.getLastName());
+        ldapObject.setAttribute(ldapFullNameAttrName, fullName);
+
+        if (isReadOnly(mapperModel)) {
+            ldapObject.addReadOnlyAttributeName(ldapFullNameAttrName);
+        }
+    }
+
+    @Override
+    public UserModel proxy(final UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate) {
+        if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
+
+
+            AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) {
+
+                @Override
+                public void setFirstName(String firstName) {
+                    super.setFirstName(firstName);
+                    setFullNameToLDAPObject();
+                }
+
+                @Override
+                public void setLastName(String lastName) {
+                    super.setLastName(lastName);
+                    setFullNameToLDAPObject();
+                }
+
+                private void setFullNameToLDAPObject() {
+                    String fullName = getFullName(getFirstName(), getLastName());
+                    if (logger.isTraceEnabled()) {
+                        logger.tracef("Pushing full name attribute to LDAP. Full name: %s", fullName);
+                    }
+
+                    ensureTransactionStarted();
+
+                    String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+                    ldapObject.setAttribute(ldapFullNameAttrName, fullName);
+                }
+
+            };
+
+            return txDelegate;
+        } else {
+            return delegate;
+        }
+    }
+
+    @Override
+    public void beforeLDAPQuery(UserFederationMapperModel mapperModel, LDAPIdentityQuery query) {
+        String ldapFullNameAttrName = getLdapFullNameAttrName(mapperModel);
+        query.addReturningLdapAttribute(ldapFullNameAttrName);
+
+        // Change conditions and compute condition for fullName from the conditions for firstName and lastName. Right now just "equal" condition is supported
+        EqualCondition firstNameCondition = null;
+        EqualCondition lastNameCondition = null;
+        Set<Condition> conditionsCopy = new HashSet<Condition>(query.getConditions());
+        for (Condition condition : conditionsCopy) {
+            QueryParameter param = condition.getParameter();
+            if (param != null) {
+                if (param.getName().equals(UserModel.FIRST_NAME)) {
+                    firstNameCondition = (EqualCondition) condition;
+                    query.getConditions().remove(condition);
+                } else if (param.getName().equals(UserModel.LAST_NAME)) {
+                    lastNameCondition = (EqualCondition) condition;
+                    query.getConditions().remove(condition);
+                } else if (param.getName().equals(LDAPConstants.GIVENNAME)) {
+                    // Some previous mapper already converted it
+                    firstNameCondition = (EqualCondition) condition;
+                } else if (param.getName().equals(LDAPConstants.SN)) {
+                    // Some previous mapper already converted it
+                    lastNameCondition = (EqualCondition) condition;
+                }
+            }
+        }
+
+
+        String fullName = null;
+        if (firstNameCondition != null && lastNameCondition != null) {
+            fullName = firstNameCondition.getValue() + " " + lastNameCondition.getValue();
+        } else if (firstNameCondition != null) {
+            fullName = (String) firstNameCondition.getValue();
+        } else if (firstNameCondition != null) {
+            fullName = (String) lastNameCondition.getValue();
+        } else {
+            return;
+        }
+        EqualCondition fullNameCondition = new EqualCondition(new QueryParameter(ldapFullNameAttrName), fullName);
+        query.getConditions().add(fullNameCondition);
+    }
+
+    protected String getLdapFullNameAttrName(UserFederationMapperModel mapperModel) {
+        String ldapFullNameAttrName = mapperModel.getConfig().get(LDAP_FULL_NAME_ATTRIBUTE);
+        return ldapFullNameAttrName == null ? LDAPConstants.CN : ldapFullNameAttrName;
+    }
+
+    protected String getFullName(String firstName, String lastName) {
+        if (firstName != null && lastName != null) {
+            return firstName + " " + lastName;
+        } else if (firstName != null) {
+            return firstName;
+        } else if (lastName != null) {
+            return lastName;
+        } else {
+            return LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
+        }
+    }
+
+    private boolean isReadOnly(UserFederationMapperModel mapperModel) {
+        return LDAPUtils.parseBooleanParameter(mapperModel, READ_ONLY);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
index 4361004..de0b834 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java
@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
 import org.keycloak.federation.ldap.idm.model.LDAPObject;
 import org.keycloak.federation.ldap.idm.query.Condition;
 import org.keycloak.federation.ldap.idm.query.QueryParameter;
@@ -42,6 +43,8 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
 
     public static final String USER_MODEL_ATTRIBUTE = "user.model.attribute";
     public static final String LDAP_ATTRIBUTE = "ldap.attribute";
+
+    // TODO: Merge with fullname mapper
     public static final String READ_ONLY = "read.only";
 
     @Override
@@ -56,7 +59,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
 
     @Override
     public String getId() {
-        return "ldap-user-attribute-mapper";
+        return "user-attribute-ldap-mapper";
     }
 
     @Override
@@ -104,18 +107,52 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
     public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapObject, UserModel delegate) {
         if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
 
-            // This assumes that mappers are sorted by type! Maybe improve...
-            TxAwareLDAPUserModelDelegate txDelegate;
-            if (delegate instanceof TxAwareLDAPUserModelDelegate) {
-                // We will reuse already existing delegate and just register our mapped attribute in existing transaction.
-                txDelegate = (TxAwareLDAPUserModelDelegate) delegate;
-            } else {
-                txDelegate = new TxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject);
-            }
+            final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
+            final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
+
+            AbstractTxAwareLDAPUserModelDelegate txDelegate = new AbstractTxAwareLDAPUserModelDelegate(delegate, ldapProvider, ldapObject) {
+
+                @Override
+                public void setAttribute(String name, String value) {
+                    setLDAPAttribute(name, value);
+
+                    super.setAttribute(name, value);
+                }
+
+                @Override
+                public void setEmail(String email) {
+                    setLDAPAttribute(UserModel.EMAIL, email);
+
+                    super.setEmail(email);
+                }
+
+                @Override
+                public void setLastName(String lastName) {
+                    setLDAPAttribute(UserModel.LAST_NAME, lastName);
+
+                    super.setLastName(lastName);
+                }
+
+                @Override
+                public void setFirstName(String firstName) {
+                    setLDAPAttribute(UserModel.FIRST_NAME, firstName);
+
+                    super.setFirstName(firstName);
+                }
+
+                protected void setLDAPAttribute(String modelAttrName, String value) {
+                    if (modelAttrName.equals(userModelAttrName)) {
+                        if (logger.isTraceEnabled()) {
+                            logger.tracef("Pushing user attribute to LDAP. Model attribute name: %s, LDAP attribute name: %s, Attribute value: %s", modelAttrName, ldapAttrName, value);
+                        }
+
+                        ensureTransactionStarted();
+
+                        ldapObject.setAttribute(ldapAttrName, value);
+                    }
+                }
 
-            String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
-            String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
-            txDelegate.addMappedAttribute(userModelAttrName, ldapAttrName);
+            };
 
             return txDelegate;
         } else {
@@ -144,7 +181,6 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
     }
 
     private boolean isReadOnly(UserFederationMapperModel mapperModel) {
-        String readOnly = mapperModel.getConfig().get(READ_ONLY);
-        return Boolean.parseBoolean(readOnly);
+        return LDAPUtils.parseBooleanParameter(mapperModel, READ_ONLY);
     }
 }
diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationMapper b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationMapper
index 7e3a77b..9d85b20 100644
--- a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationMapper
+++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.UserFederationMapper
@@ -1 +1,2 @@
-org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper
\ No newline at end of file
+org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper
+org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index f025647..3f6bec4 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -227,4 +227,7 @@ public class UserModelDelegate implements UserModel {
         return delegate.revokeConsentForClient(clientId);
     }
 
+    public UserModel getDelegate() {
+        return delegate;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index df9f26f..eeeeedf 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -957,26 +957,38 @@ public class RealmAdapter implements RealmModel {
     public List<UserFederationMapperModel> getUserFederationMappers() {
         // TODO: Some hardcoded stuff...
         List<UserFederationMapperModel> mappers = new ArrayList<UserFederationMapperModel>();
-        mappers.add(createMapperModel("usn", "usernameMapper", "ldap-user-attribute-mapper",
+
+        mappers.add(createMapperModel("usn", "usernameMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", UserModel.USERNAME,
                 "ldap.attribute", LDAPConstants.UID));
-        mappers.add(createMapperModel("fn", "firstNameMapper", "ldap-user-attribute-mapper",
+
+        // Uncomment this for CN + SN config
+        /*mappers.add(createMapperModel("fn", "firstNameMapper", "user-attribute-ldap-mapper",
+                "user.model.attribute", UserModel.FIRST_NAME,
+                "ldap.attribute", LDAPConstants.CN));*/
+
+        // Uncomment this for CN + SN + givenname config
+        mappers.add(createMapperModel("fn", "firstNameMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", UserModel.FIRST_NAME,
-                "ldap.attribute", LDAPConstants.CN));
-        mappers.add(createMapperModel("ln", "lastNameMapper", "ldap-user-attribute-mapper",
+                "ldap.attribute", LDAPConstants.GIVENNAME));
+        mappers.add(createMapperModel("fulln", "fullNameMapper", "full-name-ldap-mapper",
+                "ldap.full.name.attribute", LDAPConstants.CN));
+
+        mappers.add(createMapperModel("ln", "lastNameMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", UserModel.LAST_NAME,
                 "ldap.attribute", LDAPConstants.SN));
-        mappers.add(createMapperModel("emailMpr", "emailMapper", "ldap-user-attribute-mapper",
+
+        mappers.add(createMapperModel("emailMpr", "emailMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", UserModel.EMAIL,
                 "ldap.attribute", LDAPConstants.EMAIL));
-        mappers.add(createMapperModel("postalCodeMpr", "postalCodeMapper", "ldap-user-attribute-mapper",
+        mappers.add(createMapperModel("postalCodeMpr", "postalCodeMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", "postal_code",
                 "ldap.attribute", LDAPConstants.POSTAL_CODE));
-        mappers.add(createMapperModel("createdDateMpr", "createTimeStampMapper", "ldap-user-attribute-mapper",
+        mappers.add(createMapperModel("createdDateMpr", "createTimeStampMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", LDAPConstants.CREATE_TIMESTAMP,
                 "ldap.attribute", LDAPConstants.CREATE_TIMESTAMP,
                 "read.only", "true"));
-        mappers.add(createMapperModel("modifyDateMpr", "modifyTimeStampMapper", "ldap-user-attribute-mapper",
+        mappers.add(createMapperModel("modifyDateMpr", "modifyTimeStampMapper", "user-attribute-ldap-mapper",
                 "user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP,
                 "ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP,
                 "read.only", "true"));