keycloak-uncached
Changes
federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java 98(+56 -42)
Details
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java
index 36ff34a..1a9f7c0 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -92,7 +92,7 @@ public class LDAPIdentityStore implements IdentityStore {
         }
 
         String entryDN = ldapObject.getDn().toString();
-        BasicAttributes ldapAttributes = extractAttributes(ldapObject, true);
+        BasicAttributes ldapAttributes = extractAttributesForSaving(ldapObject, true);
         this.operationManager.createSubContext(entryDN, ldapAttributes);
         ldapObject.setUuid(getEntryIdentifier(ldapObject));
 
@@ -105,7 +105,7 @@ public class LDAPIdentityStore implements IdentityStore {
     public void update(LDAPObject ldapObject) {
         checkRename(ldapObject);
 
-        BasicAttributes updatedAttributes = extractAttributes(ldapObject, false);
+        BasicAttributes updatedAttributes = extractAttributesForSaving(ldapObject, false);
         NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
 
         String entryDn = ldapObject.getDn().toString();
@@ -396,47 +396,36 @@ public class LDAPIdentityStore implements IdentityStore {
     }
 
 
-    protected BasicAttributes extractAttributes(LDAPObject ldapObject, boolean isCreate) {
+    protected BasicAttributes extractAttributesForSaving(LDAPObject ldapObject, boolean isCreate) {
         BasicAttributes entryAttributes = new BasicAttributes();
 
         for (Map.Entry<String, Set<String>> attrEntry : ldapObject.getAttributes().entrySet()) {
             String attrName = attrEntry.getKey();
             Set<String> attrValue = attrEntry.getValue();
 
-            // ldapObject.getReadOnlyAttributeNames() are lower-cased
-            if (!ldapObject.getReadOnlyAttributeNames().contains(attrName.toLowerCase()) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
-
-                if (attrValue == null) {
-                    // Shouldn't happen
-                    logger.warnf("Attribute '%s' is null on LDAP object '%s' . Using empty value to be saved to LDAP", attrName, ldapObject.getDn().toString());
-                    attrValue = Collections.emptySet();
-                }
+            if (attrValue == null) {
+                // Shouldn't happen
+                logger.warnf("Attribute '%s' is null on LDAP object '%s' . Using empty value to be saved to LDAP", attrName, ldapObject.getDn().toString());
+                attrValue = Collections.emptySet();
+            }
 
-                // Ignore empty attributes during create
-                if (isCreate && attrValue.isEmpty()) {
-                    continue;
+            if (
+                // Ignore empty attributes on create (changetype: add)
+                !(isCreate && attrValue.isEmpty()) &&
+
+                // Since we're extracting for saving, skip read-only attributes. ldapObject.getReadOnlyAttributeNames() are lower-cased
+                !ldapObject.getReadOnlyAttributeNames().contains(attrName.toLowerCase()) &&
+
+                // Only extract RDN for create since it can't be changed on update
+                (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))
+            ) {
+                if (getConfig().getBinaryAttributeNames().contains(attrName)) {
+                    // Binary attribute
+                    entryAttributes.put(createBinaryBasicAttribute(attrName, attrValue));
+                } else {
+                    // Text attribute
+                    entryAttributes.put(createBasicAttribute(attrName, attrValue));
                 }
-
-                BasicAttribute attr = new BasicAttribute(attrName);
-                for (String val : attrValue) {
-                    if (val == null || val.toString().trim().length() == 0) {
-                        val = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
-                    }
-
-                    if (getConfig().getBinaryAttributeNames().contains(attrName)) {
-                        // Binary attribute
-                        try {
-                            byte[] bytes = Base64.decode(val);
-                            attr.add(bytes);
-                        } catch (IOException ioe) {
-                            logger.warnf("Wasn't able to Base64 decode the attribute value. Ignoring attribute update. LDAP DN: %s, Attribute: %s, Attribute value: %s" + ldapObject.getDn(), attrName, attrValue);
-                        }
-                    } else {
-                        attr.add(val);
-                    }
-                }
-
-                entryAttributes.put(attr);
             }
         }
 
@@ -446,13 +435,6 @@ public class LDAPIdentityStore implements IdentityStore {
 
             for (String objectClassValue : ldapObject.getObjectClasses()) {
                 objectClassAttribute.add(objectClassValue);
-
-                if ((objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_NAMES)
-                        || objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_ENTRIES)
-                        || objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) &&
-                        (entryAttributes.get(LDAPConstants.MEMBER) == null)) {
-                    entryAttributes.put(LDAPConstants.MEMBER, LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
-                }
             }
 
             entryAttributes.put(objectClassAttribute);
@@ -461,6 +443,38 @@ public class LDAPIdentityStore implements IdentityStore {
         return entryAttributes;
     }
 
+    private BasicAttribute createBasicAttribute(String attrName, Set<String> attrValue) {
+        BasicAttribute attr = new BasicAttribute(attrName);
+
+        for (String value : attrValue) {
+            if (value == null || value.trim().length() == 0) {
+                value = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
+            }
+
+            attr.add(value);
+        }
+
+        return attr;
+    }
+
+    private BasicAttribute createBinaryBasicAttribute(String attrName, Set<String> attrValue) {
+        BasicAttribute attr = new BasicAttribute(attrName);
+
+        for (String value : attrValue) {
+            if (value == null || value.trim().length() == 0) {
+                value = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
+            }
+
+            try {
+                byte[] bytes = Base64.decode(value);
+                attr.add(bytes);
+            } catch (IOException ioe) {
+                logger.warnf("Wasn't able to Base64 decode the attribute value. Ignoring attribute update. Attribute: %s, Attribute value: %s", attrName, attrValue);
+            }
+        }
+
+        return attr;
+    }
 
     protected String getEntryIdentifier(final LDAPObject ldapObject) {
         try {
                diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java
index 21f4bae..4f05ad4 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java
@@ -128,13 +128,24 @@ public class LDAPUtils {
     // roles & groups
 
     public static LDAPObject createLDAPGroup(LDAPStorageProvider ldapProvider, String groupName, String groupNameAttribute, Collection<String> objectClasses,
-                                             String parentDn, Map<String, Set<String>> additionalAttributes) {
+                                             String parentDn, Map<String, Set<String>> additionalAttributes, String membershipLdapAttribute) {
         LDAPObject ldapObject = new LDAPObject();
 
         ldapObject.setRdnAttributeName(groupNameAttribute);
         ldapObject.setObjectClasses(objectClasses);
         ldapObject.setSingleAttribute(groupNameAttribute, groupName);
 
+        for (String objectClassValue : objectClasses) {
+            // On MSAD with object class "group", empty member must not be added. Specified object classes typically
+            // require empty member attribute if no members have joined yet
+            if ((objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_NAMES)
+                    || objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_ENTRIES)
+                    || objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) &&
+                    additionalAttributes.get(membershipLdapAttribute) == null) {
+                ldapObject.setSingleAttribute(membershipLdapAttribute, LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
+            }
+        }
+
         LDAPDn roleDn = LDAPDn.fromString(parentDn);
         roleDn.addFirst(groupNameAttribute, groupName);
         ldapObject.setDn(roleDn);
                diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java
index f6a7c53..66ec3c3 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java
@@ -122,7 +122,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
 
     public LDAPObject createLDAPGroup(String groupName, Map<String, Set<String>> additionalAttributes) {
         LDAPObject ldapGroup = LDAPUtils.createLDAPGroup(ldapProvider, groupName, config.getGroupNameLdapAttribute(), config.getGroupObjectClasses(ldapProvider),
-                config.getGroupsDn(), additionalAttributes);
+                config.getGroupsDn(), additionalAttributes, config.getMembershipLdapAttribute());
 
         logger.debugf("Creating group [%s] to LDAP with DN [%s]", groupName, ldapGroup.getDn().toString());
         return ldapGroup;
                diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java
index 36c1e0d..cee571f 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/role/RoleLDAPStorageMapper.java
@@ -245,7 +245,7 @@ public class RoleLDAPStorageMapper extends AbstractLDAPStorageMapper implements 
 
     public LDAPObject createLDAPRole(String roleName) {
         LDAPObject ldapRole = LDAPUtils.createLDAPGroup(ldapProvider, roleName, config.getRoleNameLdapAttribute(), config.getRoleObjectClasses(ldapProvider),
-                config.getRolesDn(), Collections.<String, Set<String>>emptyMap());
+                config.getRolesDn(), Collections.<String, Set<String>>emptyMap(), config.getMembershipLdapAttribute());
 
         logger.debugf("Creating role [%s] to LDAP with DN [%s]", roleName, ldapRole.getDn().toString());
         return ldapRole;