Search code examples
javaspringunit-testingldapspring-ldap

Spring Ldap unit testing with custom schema definition


I am trying to setup embedded ldap for unit test with Spring Ldap. But I need to use a custom schema for custom objectClasses/attributes definitions. How can I configure it with Spring Ldap test (LdapTestUtils?)

Actually if I run test, it fail saying that my custom objectClass "myOb" is not defined in the schema with the following message :

org.springframework.ldap.UncategorizedLdapException: Failed to populate LDIF; nested exception is javax.naming.directory.NoSuchAttributeException: [LDAP: error code 16 - NO_SUCH_ATTRIBUTE: failed for     Add Request :
...
: OID for name 'myOb' was not found within the OID registry]; remaining name 'cn=123456, ou=MyUser, o=company.com'

If I comment objectClass: myOb from ldif, the test fail with a null value (attribute is not read).

Here is my test class :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LdapConfiguration.class, loader = AnnotationConfigContextLoader.class)
public class LdapTest {

    // Ldap port
    private static final int LDAP_PORT = 18880;

    // Base DN for test data
    private static final LdapName baseName = LdapUtils.newLdapName("o=company.com");

    @Autowired
    LdapTemplate ldapTemplate;

    @BeforeClass
    public static void setupBeforeClass() {
        LdapTestUtils.startEmbeddedServer(LDAP_PORT, baseName.toString(), "ldaptest");
        // How to load schema definition ?
    }

    @AfterClass
    public static void teardownAfterClass() throws Exception {
        LdapTestUtils.shutdownEmbeddedServer();
    }

    @Before
    public void setup() throws Exception {
        LdapTestUtils.cleanAndSetup(ldapTemplate.getContextSource(), baseName, new ClassPathResource("ldap/test-users.ldif"));
    }


    @Test
    public void testSearchLdap() throws Exception {     
        String myObId = ldapTemplate.lookup(LdapNameBuilder.newInstance("ou=MyUser, o=company.com").add("cn", "123456").build(), new AbstractContextMapper<String>() {
            @Override
            protected String doMapFromContext(DirContextOperations ctx) {
                return ctx.getStringAttribute("myObId"); // custom type
            }           
        });
        Assert.assertNotNull(myObId); // myObId is null if I comment `objectClass: myOb` !
    }
}

and my ldif :

dn: ou=MyUser, o=company.com
ou: User
description: MyUser
objectClass: top
objectClass: organizationalunit

dn: cn=123456, ou=MyUser, o=company.com
objectClass: top
objectClass: person
objectClass: myOb
cn: 123456
sn: 823456
myObId: TEST

Solution

  • I don't know how to do it with Spring Ldap... But I use Unboundid InMemoryDirectoryServer in my unit tests. This implementation of the server doesn't restrict any custom objectClasses/attributes definitions. If you want, I can share my JUnitRule here. The rule starts InMemory server and loads a ldiff into it

    UPDATED:

    public class LdapServerRule extends ExternalResource {
    private static final Log LOG = LogFactory
            .getLog(LdapServerRule.class);
    
    public static final String DefaultDn = "cn=Directory Manager";
    public static final String DefaultPassword = "password";
    private String baseDn;
    private String dn;
    private String password;
    private String lDiffPath;
    private InMemoryDirectoryServer server;
    private int listenPort;
    
    public LdapServerRule(String baseDn, String lDiffPath) {
        this(baseDn, lDiffPath, 0);
    }
    
    
    public LdapServerRule(String baseDn, String lDiffPath, int listenPort) {
        this.lDiffPath = lDiffPath;
        this.baseDn = baseDn;
        this.dn = DefaultDn;
        this.password = DefaultPassword;
        this.listenPort = listenPort;
    
    }
    
    @Override
    protected void before() {
        start();
    }
    
    @Override
    protected void after() {
        stop();
    }
    
    public int getRunningPort() {
        return getServer().getListenPort();
    }
    
    private void start() {
        InMemoryDirectoryServerConfig config;
    
        try {
            LOG.info("LDAP server " + toString() + " starting...");
            config = new InMemoryDirectoryServerConfig(getBaseDn());
            config.addAdditionalBindCredentials(getDn(),
                    getPassword());
            config.setSchema(null);
            config.setListenerConfigs(
                    InMemoryListenerConfig.createLDAPConfig("LDAP", getListenPort()));
            setServer(new InMemoryDirectoryServer(config));
    
            getServer().importFromLDIF(true, getLDiffPath());
            getServer().startListening();
            LOG.info("LDAP server " + toString() + " started. Listen on port " + getServer().getListenPort());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    
    }
    
    private void stop() {
        server.shutDown(true);
        LOG.info("LDAP server " + toString() + " stopped");
    }
    
    public String getBaseDn() {
        return baseDn;
    }
    
    public String getDn() {
        return dn;
    }
    
    public String getPassword() {
        return password;
    }
    
    public InMemoryDirectoryServer getServer() {
        return server;
    }
    
    public void setServer(InMemoryDirectoryServer server) {
        this.server = server;
    }
    
    public String getLDiffPath() {
        return lDiffPath;
    }
    
    public int getListenPort() {
        return listenPort;
    }
    
    @Override
    public String toString() {
        return com.google.common.base.Objects.toStringHelper(this)
                .add("baseDn", baseDn)
                .add("listenPort", listenPort)
                .toString();
    }
    

    }

    You can use this rule like this

    @ClassRule
    public static final LdapServerRule LDAP_RULE =
            new LdapServerRule("dc=mmkauth", resourceFilePath("data.ldiff"));
    

    LDAP_RULE.getListenPort() returns actual port for connection, or you can pass the port directly into the constructor