keycloak/keycloak

Delete user property from LDAP federation storage

Aperta

#14.508 aperta il 20 set 2022

 (4 commenti) (3 reazioni) (0 assegnatari)Java (8346 fork)batch import
area/ldaphelp wantedkind/enhancementpriority/normalstatus/auto-bumpteam/core-iam

Metriche repository

Star
 (34.398 stelle)
Metriche merge PR
 (Merge medio 6g 19h) (384 PR mergiate in 30 g)

Descrizione

Describe the bug

If a user property mapped from LDAP User Federation Storage shall be updated/removed in the origin, Keycloak (KC) uses a LDAP replace operation

public void update(LDAPObject ldapObject)

    checkRename(ldapObject);

    BasicAttributes updatedAttributes = extractAttributesForSaving(ldapObject, false);

protected BasicAttributes extractAttributesForSaving(LDAPObject ldapObject, boolean isCreate)

    ...
            } else {
                // Text attribute
                entryAttributes.put(createBasicAttribute(attrName, attrValue));
            }
    ...

which is fine in principle, but if the attibute as no longer any value at all

private BasicAttribute createBasicAttribute(String attrName, Set attrValue)

    BasicAttribute attr = new BasicAttribute(attrName);

    for (String value : attrValue) {
        if (value == null || value.trim().length() == 0) {
            value = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
        }
    ...

KC simply replaces the previous value with a single space as an empty value

public static final String EMPTY_ATTRIBUTE_VALUE = " ";

no matter if the attribute is required or not or has any format requirements or not.

So this causes a problem in situations, where the LDAP backend, e.g. ds389 is configured to check the uniqueness of attribute values, e.g the optional mail attribute of the inetOrgPerson Object Class. Instead of removing an erased email address value in the LDAP backend as well, KC sends a simple space value, which is fine for the first time, and the first time only. Trying to remove the email address of a second KC user again sends a single space value as email address update which violates the uniquness of the email attribute in the LDAP backend and therefore gets rejected.

Version

15.0.2

Expected behavior

If a non required LDAP attribute shall be removed

    ...
    if (value == null || value.length() == 0) {
        ...
    }

the attribute should get an DirContext.REMOVE_ATTRIBUTE context instead of the DirContext.REPLACE_ATTRIBUTE

public void modifyAttributes(String dn, NamingEnumeration attributes)

    {
    try {
        List<ModificationItem> modItems = new ArrayList<ModificationItem>();
        while (attributes.hasMore()) {
            ModificationItem modItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attributes.next());
            modItems.add(modItem);
        }

but at least, should not be replaced by an empty space.

Actual behavior

Keycloak sends a REPLACE operation with an empty space as attribute value invoking an exception:

17:29:17,875 WARN  [org.keycloak.services.resources.admin.UserResource] (default task-10) Could not update user!: org.keycloak.models.ModelException: Could not modify attribute for DN [uid=user,ou=users,dc=iam,dc=company,dc=at]
	at org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager.modifyAttributes(LDAPOperationManager.java:616)
	at org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager.modifyAttributes(LDAPOperationManager.java:112)
	at org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore.update(LDAPIdentityStore.java:156)
	at org.keycloak.storage.ldap.mappers.LDAPTransaction.commitImpl(LDAPTransaction.java:53)
	at org.keycloak.models.AbstractKeycloakTransaction.commit(AbstractKeycloakTransaction.java:48)
	at org.keycloak.services.DefaultKeycloakTransactionManager.commit(DefaultKeycloakTransactionManager.java:146)
	at org.keycloak.services.resources.admin.UserResource.updateUser(UserResource.java:194)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:138)
        ...
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
	at java.lang.Thread.run(Thread.java:750)
Caused by: javax.naming.directory.InvalidAttributeValueException: [LDAP: error code 19 - Another entry with the same attribute value already exists (attribute: "mail ")]; remaining name 'uid=user,ou=users,dc=iam,dc=company,dc=at'
	at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3274)
	at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3207)
	at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2998)
	at com.sun.jndi.ldap.LdapCtx.c_modifyAttributes(LdapCtx.java:1503)
	at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_modifyAttributes(ComponentDirContext.java:277)
	at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.modifyAttributes(PartialCompositeDirContext.java:192)
	at javax.naming.directory.InitialDirContext.modifyAttributes(InitialDirContext.java:172)
	at javax.naming.directory.InitialDirContext.modifyAttributes(InitialDirContext.java:172)
	at org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager$7.execute(LDAPOperationManager.java:597)
        ...

Furthermore, KC catches the exception and removes the attribute from its internal storage. So at least for some time (depending on the User Federation Storage Sync Settings), the LDAP backend and KC user storage, are out of sync!

How to Reproduce?

  1. Create a new LDAP ds389 Backend

    On a RHEL 7.9 system we are currently using

    [user@host ~]$ rpm -qa 389*
    389-console-1.1.19-6.el7.noarch
    389-ds-base-1.3.10.2-16.el7_9.x86_64
    389-adminutil-1.1.22-2.el7.x86_64
    389-ds-base-libs-1.3.10.2-16.el7_9.x86_64
    [user@host ~]$ 
    

    whereas, the server version shouldn't make any difference to reproduce the bug

  2. Create a new KC realm with default settings

  3. Add some WRITEABLE, Red Hat Directory Server Vendor, User Federation Storage, with at least an email user-attribute-ldap-mapper and switch all its options, e.g. Is Mandatory in LDAP to OFF. Leave all other non required User Federation Settings on default values, e.g. Sync Settings should be OFF

  4. Create 2 new users in the KC Admin Console including unique email addresses for both of them

  5. Remove the email address of the first user

  6. Try to remove the email address of the second user

Whereas operation nr. 6 succeeds, removing the email address of the second user should fail as described:

  • email address is removed on KC side
  • email address is NOT removed on LDAP side

If one clicks Removed imported users in the User Federation config and searches both users in the Admin Console again, the email address of the second user gets fetched again from the LDAP backend

Anything else?

This bug has already been reported at least once with KEYCLOAK-5117

Guida contributor