diff --git a/modules/ROOT/pages/testing.adoc b/modules/ROOT/pages/testing.adoc index 76224492..e1a21161 100644 --- a/modules/ROOT/pages/testing.adoc +++ b/modules/ROOT/pages/testing.adoc @@ -182,17 +182,19 @@ testCompile "com.unboundid:unboundid-ldapsdk:3.1.1" The following bean definition creates an embedded LDAP server: ==== -[source,xml] +[source,java] ---- - - - - - +@Bean +EmbeddedLdapServer embeddedLdapServer() { + return EmbeddedLdapServer.withPartitionSuffix("dc=jayway,dc=se") + .partitionName("jayway") + .port(18881) + .configurationCustomizer((config) -> config.setCodeLogDetails(tempLogFile, true)) + .build(); +} ---- -==== -`spring-ldap-test` provides a way to populate the LDAP server by using `org.springframework.ldap.test.unboundid.LdifPopulator`. To use it, create a bean similar to the following: +Alternatively, you can use the `org.springframework.ldap.test.unboundid.LdifPopulator` to create and populate the LDAP server. To use it, create a bean similar to the following: ==== [source,xml] diff --git a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java index b764493c..94379ae4 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java +++ b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServer.java @@ -16,6 +16,13 @@ package org.springframework.ldap.test.unboundid; +import java.util.List; +import java.util.function.Consumer; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; @@ -24,6 +31,7 @@ import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Helper class for embedded Unboundid ldap server. @@ -46,26 +54,29 @@ public final class EmbeddedLdapServer implements AutoCloseable { } /** - * Creates and starts new embedded LDAP server. + * Creates a new {@link Builder} with a given partition suffix. + * + * @since 3.3 */ + public static Builder withPartitionSuffix(String partitionSuffix) { + return new Builder(partitionSuffix); + } + + /** + * Creates and starts new embedded LDAP server. + * @deprecated Use the builder pattern exposed via + * {@link #withPartitionSuffix(String)} instead. + */ + @Deprecated(since = "3.3") public static EmbeddedLdapServer newEmbeddedServer(String defaultPartitionName, String defaultPartitionSuffix, - int port) throws Exception { - InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(defaultPartitionSuffix); - config.addAdditionalBindCredentials("uid=admin,ou=system", "secret"); + int port) { + EmbeddedLdapServer server = EmbeddedLdapServer.withPartitionSuffix(defaultPartitionSuffix) + .partitionName(defaultPartitionName) + .port(port) + .build(); - config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port)); - - config.setEnforceSingleStructuralObjectClass(false); - config.setEnforceAttributeSyntaxCompliance(true); - - Entry entry = new Entry(new DN(defaultPartitionSuffix)); - entry.addAttribute("objectClass", "top", "domain", "extensibleObject"); - entry.addAttribute("dc", defaultPartitionName); - - InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config); - directoryServer.add(entry); - directoryServer.startListening(); - return new EmbeddedLdapServer(directoryServer); + server.start(); + return server; } /** @@ -102,4 +113,119 @@ public final class EmbeddedLdapServer implements AutoCloseable { this.directoryServer.shutDown(true); } + /** + * Helper class for embedded Unboundid ldap server. + * + * @author Emanuel Trandafir + * @since 3.3 + */ + public static final class Builder { + + private final String partitionSuffix; + + private String partitionName; + + private int port = 0; + + private Consumer configurationCustomizer = (__) -> { + }; + + private Builder(String partitionSuffix) { + this.partitionSuffix = partitionSuffix; + this.partitionName = leftMostElement(partitionSuffix); + } + + /** + * Sets the port for the embedded LDAP server. + * @param port the port for the embedded LDAP server. Defaults to 0 in which case + * the server should automatically choose an available port. + * @return this {@link Builder} instance. + */ + public Builder port(int port) { + this.port = port; + return this; + } + + /** + * Sets a customizer for the {@link InMemoryDirectoryServerConfig}. + * @param configurationCustomizer a {@link Consumer} function that will be applied + * to the {@link InMemoryDirectoryServerConfig} before creating the + * {@link InMemoryDirectoryServer}. The default values, it a Consumer function + * that does nothing: (config) -> {} + * @return this {@link Builder} instance. + */ + public Builder configurationCustomizer(Consumer configurationCustomizer) { + this.configurationCustomizer = configurationCustomizer; + return this; + } + + /** + * Sets the partition name for the embedded LDAP server. + * @param partitionName the partition name for the embedded LDAP server. Defaults + * to the left most element of the partition suffix. + * @return this {@link Builder} instance. + */ + public Builder partitionName(String partitionName) { + this.partitionName = partitionName; + return this; + } + + /** + * Builds and returns a {@link EmbeddedLdapServer}. + *

+ * In order to start the server, you should call + * {@link EmbeddedLdapServer#start()}. + * @return a new {@link EmbeddedLdapServer}. + */ + public EmbeddedLdapServer build() { + try { + InMemoryDirectoryServerConfig config = inMemoryDirectoryServerConfig(this.partitionSuffix, this.port); + this.configurationCustomizer.accept(config); + + Entry entry = ldapEntry(this.partitionName, this.partitionSuffix); + InMemoryDirectoryServer directoryServer = inMemoryDirectoryServer(config, entry); + return new EmbeddedLdapServer(directoryServer); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + static String leftMostElement(String partitionSuffix) { + try { + List rdns = new LdapName(partitionSuffix).getRdns(); + return CollectionUtils.lastElement(rdns).getValue().toString(); + } + catch (InvalidNameException ex) { + throw new RuntimeException(ex); + } + } + + private static InMemoryDirectoryServerConfig inMemoryDirectoryServerConfig(String partitionSuffix, int port) + throws LDAPException { + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(partitionSuffix); + config.addAdditionalBindCredentials("uid=admin,ou=system", "secret"); + config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", port)); + config.setEnforceSingleStructuralObjectClass(false); + config.setEnforceAttributeSyntaxCompliance(true); + return config; + } + + private static Entry ldapEntry(String defaultPartitionName, String defaultPartitionSuffix) + throws LDAPException { + Entry entry = new Entry(new DN(defaultPartitionSuffix)); + entry.addAttribute("objectClass", "top", "domain", "extensibleObject"); + entry.addAttribute("dc", defaultPartitionName); + return entry; + } + + private static InMemoryDirectoryServer inMemoryDirectoryServer(InMemoryDirectoryServerConfig config, + Entry entry) throws LDAPException { + InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config); + directoryServer.add(entry); + return directoryServer; + } + + } + } diff --git a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java index 360a6867..1db35095 100644 --- a/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java +++ b/test-support/src/main/java/org/springframework/ldap/test/unboundid/EmbeddedLdapServerFactoryBean.java @@ -48,7 +48,13 @@ public class EmbeddedLdapServerFactoryBean extends AbstractFactoryBean config.setCodeLogDetails(tempLogFile, true)); + + try (EmbeddedLdapServer server = serverBuilder.build()) { + server.start(); + + ldapTemplate("dc=jayway,dc=se", port).search(LdapQueryBuilder.query().where("objectclass").is("person"), + new AttributesMapper<>() { + public String mapFromAttributes(Attributes attrs) throws NamingException { + return (String) attrs.get("cn").get(); + } + }); + } + + assertThat(Path.of(tempLogFile)) + .as("Applying the custom configuration should create a log file and populate it with the request") + .isNotEmptyFile(); + } + static boolean isPortOpen(int port) { try (Socket ignored = new Socket("localhost", port)) { return true; @@ -112,4 +166,14 @@ public class EmbeddedLdapServerTests { } } + static LdapTemplate ldapTemplate(String base, int port) { + LdapContextSource ctx = new LdapContextSource(); + ctx.setBase(base); + ctx.setUrl("ldap://127.0.0.1:" + port); + ctx.setUserDn("uid=admin,ou=system"); + ctx.setPassword("secret"); + ctx.afterPropertiesSet(); + return new LdapTemplate(ctx); + } + }