1. Issue description
====================
1.1) At LDAP level, it is possible to create ditrectly DN which are of type
dn: mail=foo@bar.com,ou=people, dc=example,dc=com
Here the rdn is the mail attribute.
Note:
The feasability of creating user have such dn at ldap level has been tested at ldap level.
1.2) For compatibility reason with his existing LDAP end user needs to have mail being the the RDN.
2) How to trigger error
-----------------------
The error can be easily triggered trying to add a user with username user13@example.com
Adding a user is triggering following error:
eycloak.services.resources.KeycloakApplication
12:01:50,687 INFO [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool – 54) RESTEASY002220: Adding singleton resource org.keycloak.services.resources.RealmsResource from Application class org.keycloak.services.resources.KeycloakApplication
12:01:50,753 INFO [org.wildfly.extension.undertow] (ServerService Thread Pool – 54) WFLYUT0021: Registered web context: '/auth' for server 'default-server'
12:01:50,837 INFO [org.jboss.as.server] (ServerService Thread Pool – 42) WFLYSRV0010: Deployed "keycloak-server.war" (runtime-name : "keycloak-server.war")
12:01:50,877 INFO [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server
12:01:50,881 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 999-SNAPSHOT (WildFly Core 18.1.1.Final) started in 8836ms - Started 573 of 851 services (576 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml
12:01:50,883 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
12:01:50,883 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
12:01:53,585 WARN [org.keycloak.events] (default task-1) type=REFRESH_TOKEN_ERROR, realmId=6cb64cff-e167-446d-a671-97de3320bb7a, clientId=security-admin-console, userId=4d0b8b2d-5f00-4367-ac61-8c5e464310c4, ipAddress=127.0.0.1, error=invalid_token, grant_type=refresh_token, refresh_token_type=Refresh, refresh_token_id=f24b9551-87b9-4bcd-af5c-60f6c3ec9840, client_auth_method=client-secret
12:02:43,958 WARN [org.keycloak.services.resources.admin.UsersResource] (default task-3) Could not create user: org.keycloak.models.ModelException: RDN Attribute [mail] is not filled. Filled attributes: {mail=[], sn=[ ], cn=[ ], modifyTimestamp=[], createTimestamp=[]}
at org.keycloak.keycloak-ldap-federation@999-SNAPSHOT//org.keycloak.storage.ldap.LDAPUtils.computeAndSetDn(LDAPUtils.java:111)
at org.keycloak.keycloak-ldap-federation@999-SNAPSHOT//org.keycloak.storage.ldap.LDAPUtils.addUserToLDAP(LDAPUtils.java:80)
at org.keycloak.keycloak-ldap-federation@999-SNAPSHOT//org.keycloak.storage.ldap.LDAPStorageProvider.addUser(LDAPStorageProvider.java:295)
at org.keycloak.keycloak-model-legacy-private@999-SNAPSHOT//org.keycloak.storage.UserStorageManager.lambda$addUser$13(UserStorageManager.java:277)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:400)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:503)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:543)
at org.keycloak.keycloak-model-legacy-private@999-SNAPSHOT//org.keycloak.storage.UserStorageManager.addUser(UserStorageManager.java:279)
at org.keycloak.keycloak-model-infinispan@999-SNAPSHOT//org.keycloak.models.cache.infinispan.UserCacheSession.addUser(UserCacheSession.java:794)
at org.keycloak.keycloak-services@999-SNAPSHOT//org.keycloak.userprofile.AbstractUserProfileProvider$1.apply(AbstractUserProfileProvider.java:272)
at org.keycloak.keycloak-services@999-SNAPSHOT//org.keycloak.userprofile.AbstractUserProfileProvider$1.apply(AbstractUserProfileProvider.java:259)
at org.keycloak.keycloak-server-spi-private@999-SNAPSHOT//org.keycloak.userprofile.DefaultUserProfile.create(DefaultUserProfile.java:87)
at org.keycloak.keycloak-services@999-SNAPSHOT//org.keycloak.services.resources.admin.UsersResource.createUser(UsersResource.java:157)
It means that at RH-SSO level the username has to be mapped on RDN.
1.3) Configuration done
3 confifuration changes have to be done to be able to map RDN as mail for RH-SSO.
-Username Ldap attribute:
---> set to mail
-RDN LDAP attribute
---> set to mail
Username mapper
---> set to mail
3) Issue analysis
=================
The issue analysis has been done on Leycloak 18.0.2 sources.
The corruption in the function addUserToLDAP whith the stream function.
When the username is uid, the stream function is correctly retreiveing the value of the uid.
when the username is set to mail, teh strema function is returning null
LDAP Object [ dn: null , uuid: null, attributes: {mail=[], sn=[ ], cn=[ ], modifyTimestamp=[], createTimestamp=[]}, readOnly attribute names: [modifytimestamp, createtimestamp], ranges: {} ]
All the streaming needs to be be revisted to accept RDN values such as mail.
File LDAPutils.java line 74
public static LDAPObject addUserToLDAP(LDAPStorageProvider ldapProvider, RealmModel realm, UserModel user) {
LDAPObject ldapUser = new LDAPObject();
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
LDAPConfig ldapConfig = ldapStore.getConfig();
ldapUser.setRdnAttributeName(ldapConfig.getRdnLdapAttribute());
ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());
LDAPMappersComparator ldapMappersComparator = new LDAPMappersComparator(ldapConfig);
realm.getComponentsStream(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName())
.sorted(ldapMappersComparator.sortAsc())
.forEachOrdered(mapperModel ->
{
LDAPStorageMapper ldapMapper = ldapProvider.getMapperManager().getMapper(mapperModel);
ldapMapper.onRegisterUserToLDAP(ldapUser, user, realm);
}
);
LDAPUtils.computeAndSetDn(ldapConfig, ldapUser);
ldapStore.add(ldapUser);
return ldapUser;
}