Skip to content

Allow port=0 for ApacheDSContainer #8416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,20 @@ private PasswordCompareConfigurer() {
* embedded LDAP instance.
*
* @author Rob Winch
* @author Evgeniy Cheban
* @since 3.2
*/
public final class ContextSourceBuilder {
private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService";
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";

private static final int DEFAULT_PORT = 33389;
private static final int RANDOM_PORT = 0;

private String ldif = "classpath*:*.ldif";
private String managerPassword;
private String managerDn;
private Integer port;
private static final int DEFAULT_PORT = 33389;
private String root = "dc=springframework,dc=org";
private String url;

Expand Down Expand Up @@ -540,6 +546,10 @@ public LdapAuthenticationProviderConfigurer<B> and() {
}

private DefaultSpringSecurityContextSource build() throws Exception {
if (this.url == null) {
startEmbeddedLdapServer();
}

DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
getProviderUrl());
if (managerDn != null) {
Expand All @@ -551,26 +561,29 @@ private DefaultSpringSecurityContextSource build() throws Exception {
contextSource.setPassword(managerPassword);
}
contextSource = postProcess(contextSource);
if (url != null) {
return contextSource;
}
if (ClassUtils.isPresent("org.apache.directory.server.core.DefaultDirectoryService", getClass().getClassLoader())) {
ApacheDSContainer apacheDsContainer = new ApacheDSContainer(root, ldif);
return contextSource;
}

private void startEmbeddedLdapServer() throws Exception {
if (ClassUtils.isPresent(APACHEDS_CLASSNAME, getClass().getClassLoader())) {
ApacheDSContainer apacheDsContainer = new ApacheDSContainer(this.root, this.ldif);
apacheDsContainer.setPort(getPort());
postProcess(apacheDsContainer);
this.port = apacheDsContainer.getLocalPort();
}
else if (ClassUtils.isPresent("com.unboundid.ldap.listener.InMemoryDirectoryServer", getClass().getClassLoader())) {
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(root, ldif);
else if (ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) {
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif);
unboundIdContainer.setPort(getPort());
postProcess(unboundIdContainer);
this.port = unboundIdContainer.getPort();
}
else {
throw new IllegalStateException("Embedded LDAP server is not provided");
}
return contextSource;
}

private int getPort() {
if (port != null && port == 0) {
port = getRandomPort();
} else if (port == null) {
if (port == null) {
port = getDefaultPort();
}
return port;
Expand All @@ -580,15 +593,7 @@ private int getDefaultPort() {
try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) {
return serverSocket.getLocalPort();
} catch (IOException e) {
return getRandomPort();
}
}

private int getRandomPort() {
try (ServerSocket serverSocket = new ServerSocket(0)) {
return serverSocket.getLocalPort();
} catch (IOException e) {
return DEFAULT_PORT;
return RANDOM_PORT;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@
import java.io.IOException;
import java.net.ServerSocket;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.config.BeanIds;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.util.ClassUtils;
Expand All @@ -37,12 +39,11 @@
/**
* @author Luke Taylor
* @author Eddú Meléndez
* @author Evgeniy Cheban
*/
public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
private static final String CONTEXT_SOURCE_CLASS = "org.springframework.security.ldap.DefaultSpringSecurityContextSource";

private final Log logger = LogFactory.getLog(getClass());

/**
* Defines the Url of the ldap server to use. If not specified, an embedded apache DS
* instance will be created
Expand All @@ -66,8 +67,8 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {

/** Defines the port the LDAP_PROVIDER server should run on */
public static final String ATT_PORT = "port";
private static final String RANDOM_PORT = "0";
private static final int DEFAULT_PORT = 33389;
public static final String OPT_DEFAULT_PORT = String.valueOf(DEFAULT_PORT);

private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService";
private static final String UNBOUNID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
Expand Down Expand Up @@ -136,28 +137,22 @@ private RootBeanDefinition createEmbeddedServer(Element element,
suffix = OPT_DEFAULT_ROOT_SUFFIX;
}

String port = element.getAttribute(ATT_PORT);

if ("0".equals(port)) {
port = getRandomPort();
if (logger.isDebugEnabled()) {
logger.debug("Using default port of " + port);
}
} else if (!StringUtils.hasText(port)) {
port = getDefaultPort();
if (logger.isDebugEnabled()) {
logger.debug("Using default port of " + port);
}
}

String url = "ldap://127.0.0.1:" + port + "/" + suffix;

BeanDefinitionBuilder contextSource = BeanDefinitionBuilder
.rootBeanDefinition(CONTEXT_SOURCE_CLASS);
contextSource.addConstructorArgValue(url);
contextSource.addConstructorArgValue(suffix);
contextSource.addPropertyValue("userDn", "uid=admin,ou=system");
contextSource.addPropertyValue("password", "secret");

BeanDefinition embeddedLdapServerConfigBean = BeanDefinitionBuilder
.rootBeanDefinition(EmbeddedLdapServerConfigBean.class).getBeanDefinition();
String embeddedLdapServerConfigBeanName = parserContext.getReaderContext()
.generateBeanName(embeddedLdapServerConfigBean);

parserContext.registerBeanComponent(new BeanComponentDefinition(embeddedLdapServerConfigBean,
embeddedLdapServerConfigBeanName));

contextSource.setFactoryMethodOnBean("createEmbeddedContextSource", embeddedLdapServerConfigBeanName);

String mode = element.getAttribute("mode");
RootBeanDefinition ldapContainer = getRootBeanDefinition(mode);
ldapContainer.setSource(source);
Expand All @@ -169,9 +164,7 @@ private RootBeanDefinition createEmbeddedServer(Element element,
}

ldapContainer.getConstructorArgumentValues().addGenericArgumentValue(ldifs);
ldapContainer.getPropertyValues().addPropertyValue("port", port);

logger.info("Embedded LDAP server bean definition created for URL: " + url);
ldapContainer.getPropertyValues().addPropertyValue("port", getPort(element));

if (parserContext.getRegistry()
.containsBeanDefinition(BeanIds.EMBEDDED_APACHE_DS) ||
Expand Down Expand Up @@ -217,19 +210,46 @@ private boolean isUnboundidEnabled(String mode) {
return "unboundid".equals(mode) || ClassUtils.isPresent(UNBOUNID_CLASSNAME, getClass().getClassLoader());
}

private String getPort(Element element) {
String port = element.getAttribute(ATT_PORT);
return (StringUtils.hasText(port) ? port : getDefaultPort());
}

private String getDefaultPort() {
try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) {
return String.valueOf(serverSocket.getLocalPort());
} catch (IOException e) {
return getRandomPort();
return RANDOM_PORT;
}
}

private String getRandomPort() {
try (ServerSocket serverSocket = new ServerSocket(0)) {
return String.valueOf(serverSocket.getLocalPort());
} catch (IOException e) {
return String.valueOf(DEFAULT_PORT);
private static class EmbeddedLdapServerConfigBean implements ApplicationContextAware {

private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

@SuppressWarnings("unused")
private DefaultSpringSecurityContextSource createEmbeddedContextSource(String suffix) {
int port;
if (ClassUtils.isPresent(APACHEDS_CLASSNAME, getClass().getClassLoader())) {
ApacheDSContainer apacheDSContainer = this.applicationContext.getBean(ApacheDSContainer.class);
port = apacheDSContainer.getLocalPort();
}
else if (ClassUtils.isPresent(UNBOUNID_CLASSNAME, getClass().getClassLoader())) {
UnboundIdContainer unboundIdContainer = this.applicationContext.getBean(UnboundIdContainer.class);
port = unboundIdContainer.getPort();
}
else {
throw new IllegalStateException("Embedded LDAP server is not provided");
}

String providerUrl = "ldap://127.0.0.1:" + port + "/" + suffix;

return new DefaultSpringSecurityContextSource(providerUrl);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,7 @@
* @author Luke Taylor
* @author Rob Winch
* @author Gunnar Hillert
* @author Evgeniy Cheban
* @since 3.0
*/
public class ApacheDSContainerTests {
Expand Down Expand Up @@ -212,4 +213,20 @@ private List<Integer> getDefaultPorts(int count) throws IOException {
}
}
}

@Test
public void afterPropertiesSetWhenPortIsZeroThenRandomPortIsSelected() throws Exception {
ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
server.setPort(0);
try {
server.afterPropertiesSet();

assertThat(server.getPort()).isEqualTo(0);
assertThat(server.getLocalPort()).isNotEqualTo(0);
}
finally {
server.destroy();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,7 @@
*/
package org.springframework.security.ldap.server;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -39,6 +40,7 @@
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
Expand Down Expand Up @@ -69,6 +71,7 @@
* @author Luke Taylor
* @author Rob Winch
* @author Gunnar Hillert
* @author Evgeniy Cheban
* @deprecated Use {@link UnboundIdContainer} instead because ApacheDS 1.x is no longer
* supported with no GA version to replace it.
*/
Expand All @@ -80,6 +83,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
final DefaultDirectoryService service;
LdapServer server;

private TcpTransport transport;
private ApplicationContext ctxt;
private File workingDir;

Expand All @@ -88,6 +92,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
private final JdbmPartition partition;
private final String root;
private int port = 53389;
private int localPort;

private boolean ldapOverSslEnabled;
private File keyStoreFile;
Expand Down Expand Up @@ -143,7 +148,7 @@ public void afterPropertiesSet() throws Exception {
server.setDirectoryService(service);
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here

TcpTransport transport = new TcpTransport(port);
this.transport = new TcpTransport(port);
if (ldapOverSslEnabled) {
transport.setEnableSSL(true);
server.setKeystoreFile(this.keyStoreFile.getAbsolutePath());
Expand Down Expand Up @@ -190,6 +195,15 @@ public int getPort() {
return this.port;
}

/**
* Returns the port that is resolved by {@link TcpTransport}.
*
* @return the port that is resolved by {@link TcpTransport}
*/
public int getLocalPort() {
return this.localPort;
}

/**
* If set to {@code true} will enable LDAP over SSL (LDAPs). If set to {@code true}
* {@link ApacheDSContainer#setCertificatePassord(String)} must be set as well.
Expand Down Expand Up @@ -262,6 +276,10 @@ public void start() {
logger.error("Lookup failed", e);
}

SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport);
InetSocketAddress localAddress = socketAcceptor.getLocalAddress();
this.localPort = localAddress.getPort();

running = true;

try {
Expand Down