Describe the bug

When given a valid URL such as ldap://server/dc=org%20with%20space,dc=com, the DefaultSpringSecurityContextSource sets the baseDN to dc=org%20with%20space,dc=com and not dc=org with space,dc=com.

This causes lookups to fail when setting the baseDN as it will be set to an invalid DN.

https://github.com/spring-projects/spring-security/blob/02474fdd842c63e6fa1d129d4a7e88afe12c387b/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java#L69 gets the baseDN raw path ie URL encoded https://github.com/spring-projects/spring-security/blob/02474fdd842c63e6fa1d129d4a7e88afe12c387b/ldap/src/main/java/org/springframework/security/ldap/LdapUtils.java#L135 which just about works as it is then used to manipulate the URL as a String.

However later on it is just persisted to the Base DN.

https://github.com/spring-projects/spring-security/blob/02474fdd842c63e6fa1d129d4a7e88afe12c387b/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java#L77

To Reproduce

using commit 39c4195c3557985f34dc725b79e8f3ed3d767e20 from https://github.com/jenkinsci/ldap-plugin/pull/93/commits/39c4195c3557985f34dc725b7
run mvn test -Dtest=LDAPDNEscapingTest#testSpacesInDNWithRootDN

The URL being provided to DefaultSpringSecurityContextSource in this case is ldap://localhost:52752/dc=planet%20express,dc=com

Expected behavior

The lookup of any user succeeds

but it fails.

   4.977 [id=31]    WARNING h.security.LDAPSecurityRealm#throwUnlessConfigIsIgnorable: Failed communication with ldap server Ve2UCorQ7HhmkQyz783bXg== (ldap://localhost:52752), will _not_ try the next configuration
javax.naming.NameNotFoundException: [LDAP: error code 32 - NO_SUCH_OBJECT: failed for MessageType : SEARCH_REQUEST
Message ID : 2
    SearchRequest
        baseDn : 'dc=planet%20express,dc=com'
        filter : '(uid=fry)'
        scope : whole subtree
        typesOnly : false
        Size Limit : no limit
        Time Limit : no limit
        Deref Aliases : deref Always
        attributes : '*', '+'
org.apache.directory.api.ldap.model.message.SearchRequestImpl@f86c2029: ERR_268 Cannot find a partition for dc=planet%20express,dc=com]; remaining name '/'

note the completely invalid baseDn

Sample

A link to a GitHub repository with a minimal, reproducible sample.

https://github.com/jenkinsci/ldap-plugin/pull/93/commits/39c4195c3557985f34dc725b7

Reports that include a sample will take priority over reports that do not. At times, we may require a sample, so it is good to try and include a sample up front.

Comment From: jzheaux

Thanks, @jtnord, this sounds like a reasonable change to make.

To resolve the issue, I think the rootDn needs to be correctly computed. To test it, the LDAP integration tests should stand up a container and accompanying LDIF entries whose root contains a space.

Comment From: jtnord

FWIW there is a LDIF here which was modified from the planet express which is MIT licenced.

Comment From: sjohnr

Hi @jtnord,

I see you've already worked around your issue in the meantime (so this suggestion may not be too valuable), but I wanted to check if you saw this as a viable workaround as well:

String providerUrl = "ldap://myhost:10389/dc=spring%20framework,dc=org";
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(providerUrl);
contextSource.setBase(URI.create(LdapUtils.parseRootDnFromUrl(providerUrl)).getPath());
// ...

Comment From: jtnord

Hi @jtnord,

I see you've already worked around your issue in the meantime (so this suggestion may not be too valuable), but I wanted to check if you saw this as a viable workaround as well:

java String providerUrl = "ldap://myhost:10389/dc=spring%20framework,dc=org"; DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(providerUrl); contextSource.setBase(URI.create(LdapUtils.parseRootDnFromUrl(providerUrl)).getPath()); // ...

Damn that's certainly cleaner than my workaround! Thanks

Comment From: sjohnr

Perhaps it will help if anyone encounters this in the meantime. Thanks for the feedback!