Search code examples
active-directoryldapspring-ldap

Why is my Spring LDAP search failing to return a user


I am trying to write a Spring LDAP program to search for a user's record in an Active Directory server, but it will not return any records. The same search in Apache Directory Studio works.

This Apache Directory Studio search returns my ID

When I run the query in Spring LDAP, it fails with this error.

org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-0310028C, problem 2001 (NO_OBJECT), data 0, best match of:
        'CN=Users,DC=myserver,DC=myschool,DC=edu'
 ]

It should have found my record. So what am I doing wrong? Why isn't it working for me?

Thanks.

Technical details

I started this as a Groovy project in Spring Initializr; it required Java 17.

Gradle

Here is the relevant information from the build.gradle file.

plugins {
  id 'groovy'
  id 'org.springframework.boot' version '3.3.1'
  id 'io.spring.dependency-management' version '1.1.5'
}

group = 'edu.myschool'
version = '0.0.1-SNAPSHOT'

java {
  toolchain {
    languageVersion = JavaLanguageVersion.of(17)
  }
}

repositories {
  mavenCentral()
}

ext {
  set('springShellVersion', "3.3.1")
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-data-ldap'
  implementation 'org.apache.groovy:groovy'
  implementation 'org.springframework.boot:spring-boot-starter'
  implementation 'org.springframework.shell:spring-shell-starter'

}

dependencyManagement {
  imports {
    mavenBom "org.springframework.shell:spring-shell-dependencies:${springShellVersion}"
  }
}

A complete Java test class

I wrote this class in Java because Spring programs do not necessarily work the same when they are called from Groovy. The program is, however in the Groovy source tree. The work is done by the queryForUser method.

package edu.sunyjcc.testbed;


import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.io.StringWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import javax.naming.directory.SearchControls;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.AttributesMapper;
import javax.naming.directory.Attributes;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.context.annotation.PropertySource;

/** 
 *  This is a minimal example that should demonstrate my problem.
 */
@Component
@PropertySource("classpath:application.properties")
public class HelpTicketExample {

    protected int scope = SearchControls.SUBTREE_SCOPE;
    protected boolean ignorePartialResultException = true;

    @Value("${server.urls}")
    String url;

    @Value("${manager.userdn}")
    String userDn;

    @Value("${manager.password}")
    String password;

    @Value("${username}")
    protected String usernameAttr;

    @Value("${password}")
    protected String passwordAttr;

    // protected LdapTemplate ldapTemplate;
    // protected LdapContextSource ldapContextSource;
    @Value("${basedn}")
    protected String searchBase;

    /** Work around the lack of a println in StringWriter */
    interface Printer {
        String println(String str) throws IOException;
    }

    String filter = "(&(objectClass=person)(sAMAccountName=myusername))";

    /** Print out the configuration information */
    public void showConfig(Printer p) throws IOException {
        p.println("**********************************************************************");
        p.println("* usernameAttr: " + usernameAttr);
        p.println("* searchBase:   " + searchBase);
        p.println("* filter:       " + filter);
        p.println("**********************************************************************");

    }

    public Object queryForUser(String username) throws IOException {
        StringWriter   s = new StringWriter();
        BufferedWriter b = new BufferedWriter(s);
        Printer p = (text) -> {
            b.write(text);
            b.newLine();
            return text;
        };
        //////////////////////////////////////////////////////////////////////
        // Get to work
        //////////////////////////////////////////////////////////////////////
        p.println("************************************************************" );
        p.println("queryForUser(" + username + ")");
        b.newLine();
        showConfig(p);
        //////////////////////////////////////////////////////////////////////
        LdapContextSource contextSource = new LdapContextSource();
        contextSource.setUrl(url);
        contextSource.setBase(searchBase);
        contextSource.setUserDn(userDn);
        contextSource.setPassword(password);
        contextSource.afterPropertiesSet();
        //////////////////////////////////////////////////////////////////////
        AttributesMapper<String> mapper = new AttributesMapper() {
                public String mapFromAttributes(Attributes attributes) {
                    return "Attributes found";
                }
            };
        //////////////////////////////////////////////////////////////////////
        LdapTemplate ldapTemplate = new LdapTemplate(contextSource);
        ldapTemplate.setIgnorePartialResultException(true);
        try {
            ldapTemplate.afterPropertiesSet();
            @SuppressWarnings("unchecked")
                List<String> results = ldapTemplate.search(searchBase,
                                                           filter,
                                                           scope,
                                                           mapper);
        } catch (Exception e) {
            p.println(e.toString());
        }
        //////////////////////////////////////////////////////////////////////
        b.close();
        return s.toString();
    }

    public HelpTicketExample() {
    }
}

Output

**********************************************************
queryForUser(myusername)
*********************************************************************
# usernameAttr: sAMAccountName
# searchBase:   cn=Users,dc=myserver,dc=myschool,dc=edu
# filter:       (&(objectClass=person)(sAMAccountName=myusername))
*********************************************************************
org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-0310028C, problem 2001 (NO\_OBJECT), data 0, best match of:
        'CN=Users,DC=myserver,DC=myschool,DC=edu'
 ]

Solution

  • I found that I can get results using LdapQueryBuilder.

    import static org.springframework.ldap.query.LdapQueryBuilder.query;
    

    You then use this when searching:

    def search = ldapTemplate.search(
      query()
        .attributes("cn")
        .where("objectclass").is("person")
            .and("sAMAccountName").is("myusername"),
     new AttributesMapper<String>() {
        public String mapFromAttributes(Attributes attrs) {
          return attrs.get("cn").get().toString();
    })