Skip to content

Commit

Permalink
Add unbounid support in xml
Browse files Browse the repository at this point in the history
Currently, spring-security provides apacheds integration by default. This
commit introduces a new `mode` in the `ldap-server` tag which allows to choose
beetween `apacheds` and `unboundid`. In order to keep backward compatibility
if `mode` is not set and apacheds jars are in the classpath apacheds is used
as a embedded ldap.

Fixes gh-6011
Currently, unboundid was added as a support for embbeded LDAP and it
is used on the Java Config. This commit introduces support from XML side.
Also, give the chance to users to move from apacheds to unboundid using
a new attribute `mode`.

Fixes gh-6011
  • Loading branch information
eddumelendez committed Aug 11, 2019
1 parent 0410bac commit 9b2af94
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 42 deletions.
1 change: 1 addition & 0 deletions config/spring-security-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dependencies {

testRuntime 'cglib:cglib-nodep'
testRuntime 'org.hsqldb:hsqldb'
testCompile "com.unboundid:unboundid-ldapsdk"
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public abstract class BeanIds {
+ "methodSecurityMetadataSourceAdvisor";
public static final String EMBEDDED_APACHE_DS = PREFIX
+ "apacheDirectoryServerContainer";
public static final String EMBEDDED_UNBOUNDID = PREFIX
+ "unboundidServerContainer";
public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";

public static final String DEBUG_FILTER = PREFIX + "debugFilter";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public BeanDefinition parse(Element element, ParserContext pc) {
if (!namespaceMatchesVersion(element)) {
pc.getReaderContext()
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 4.2. Please update your schema declarations to the 4.2 schema.",
+ "with Spring Security 5.2. Please update your schema declarations to the 5.2 schema.",
element);
}
String name = pc.getDelegate().getLocalName(element);
Expand Down Expand Up @@ -221,7 +221,7 @@ private boolean namespaceMatchesVersion(Element element) {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS(
"http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-4\\.2.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-5\\.2.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2019 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 @@ -17,9 +17,13 @@

import java.io.IOException;
import java.net.ServerSocket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
Expand All @@ -28,11 +32,14 @@
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.config.BeanIds;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
* @author Luke Taylor
* @author Eddú Meléndez
*/
public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
private static final String CONTEXT_SOURCE_CLASS = "org.springframework.security.ldap.DefaultSpringSecurityContextSource";
Expand Down Expand Up @@ -65,6 +72,22 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
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";

private static final String APACHEDS_CONTAINER_CLASSNAME = "org.springframework.security.ldap.server.ApacheDSContainer";
private static final String UNBOUNDID_CONTAINER_CLASSNAME = "org.springframework.security.ldap.server.UnboundIdContainer";

private Map<String, EmbeddedLdapServer> embeddedServers;

public LdapServerBeanDefinitionParser() {
Map<String, EmbeddedLdapServer> embeddedLdapServers = new HashMap<>();
embeddedLdapServers.put("apacheds", new EmbeddedLdapServer(BeanIds.EMBEDDED_APACHE_DS, APACHEDS_CLASSNAME, APACHEDS_CONTAINER_CLASSNAME));
embeddedLdapServers.put("unboundid", new EmbeddedLdapServer(BeanIds.EMBEDDED_UNBOUNDID, UNBOUNID_CLASSNAME, UNBOUNDID_CONTAINER_CLASSNAME));

this.embeddedServers = Collections.unmodifiableMap(embeddedLdapServers);
}

public BeanDefinition parse(Element elt, ParserContext parserContext) {
String url = elt.getAttribute(ATT_URL);

Expand Down Expand Up @@ -114,6 +137,7 @@ public BeanDefinition parse(Element elt, ParserContext parserContext) {
* @return the BeanDefinition for the ContextSource for the embedded server.
*
* @see ApacheDSContainer
* @see UnboundIdContainer
*/
private RootBeanDefinition createEmbeddedServer(Element element,
ParserContext parserContext) {
Expand Down Expand Up @@ -142,34 +166,78 @@ private RootBeanDefinition createEmbeddedServer(Element element,
contextSource.addPropertyValue("userDn", "uid=admin,ou=system");
contextSource.addPropertyValue("password", "secret");

RootBeanDefinition apacheContainer = new RootBeanDefinition(
"org.springframework.security.ldap.server.ApacheDSContainer", null, null);
apacheContainer.setSource(source);
apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(suffix);
String mode = element.getAttribute("mode");
RootBeanDefinition ldapContainer = getRootBeanDefinition(mode);
ldapContainer.setSource(source);
ldapContainer.getConstructorArgumentValues().addGenericArgumentValue(suffix);

String ldifs = element.getAttribute(ATT_LDIF_FILE);
if (!StringUtils.hasText(ldifs)) {
ldifs = OPT_DEFAULT_LDIF_FILE;
}

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

logger.info("Embedded LDAP server bean definition created for URL: " + url);

if (parserContext.getRegistry()
.containsBeanDefinition(BeanIds.EMBEDDED_APACHE_DS)) {
.containsBeanDefinition(BeanIds.EMBEDDED_APACHE_DS) ||
parserContext.getRegistry().containsBeanDefinition(BeanIds.EMBEDDED_UNBOUNDID)) {
parserContext.getReaderContext().error(
"Only one embedded server bean is allowed per application context",
element);
}

parserContext.getRegistry().registerBeanDefinition(BeanIds.EMBEDDED_APACHE_DS,
apacheContainer);
EmbeddedLdapServer embeddedLdapServer = resolveEmbeddedLdapServer(mode);
if (embeddedLdapServer != null) {
parserContext.getRegistry().registerBeanDefinition(embeddedLdapServer.getBeanId(),
ldapContainer);
}

return (RootBeanDefinition) contextSource.getBeanDefinition();
}

private RootBeanDefinition getRootBeanDefinition(String mode) {
if (StringUtils.hasLength(mode)) {
if (isEmbeddedServerEnabled(mode)) {
return new RootBeanDefinition(this.embeddedServers.get(mode).getContainerClass(), null, null);
}
}
else {
for (Map.Entry<String, EmbeddedLdapServer> entry : this.embeddedServers.entrySet()) {
EmbeddedLdapServer ldapServer = entry.getValue();
if (ClassUtils.isPresent(ldapServer.getClassName(), getClass().getClassLoader())) {
return new RootBeanDefinition(ldapServer.getContainerClass(), null, null);
}
}
}
throw new IllegalStateException("Embedded LDAP server is not provided");
}

private boolean isEmbeddedServerEnabled(String mode) {
EmbeddedLdapServer server = resolveEmbeddedLdapServer(mode);
return server != null;
}

private EmbeddedLdapServer resolveEmbeddedLdapServer(String mode) {
if (StringUtils.hasLength(mode)) {
if (this.embeddedServers.containsKey(mode) ||
ClassUtils.isPresent(this.embeddedServers.get(mode).getClassName(), getClass().getClassLoader())) {
return this.embeddedServers.get(mode);
}
}
else {
for (Map.Entry<String, EmbeddedLdapServer> entry : this.embeddedServers.entrySet()) {
EmbeddedLdapServer ldapServer = entry.getValue();
if (ClassUtils.isPresent(ldapServer.getClassName(), getClass().getClassLoader())) {
return ldapServer;
}
}
}
return null;
}

private String getDefaultPort() {
ServerSocket serverSocket = null;
try {
Expand All @@ -196,4 +264,31 @@ private String getDefaultPort() {
}
}
}

private class EmbeddedLdapServer {

private String beanId;

private String className;

private String containerClass;

public EmbeddedLdapServer(String beanId, String className, String containerClass) {
this.beanId = beanId;
this.className = className;
this.containerClass = containerClass;
}

public String getBeanId() {
return this.beanId;
}

public String getClassName() {
return this.className;
}

public String getContainerClass() {
return this.containerClass;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ ldap-server.attlist &=
ldap-server.attlist &=
## Optional root suffix for the embedded LDAP server. Default is "dc=springframework,dc=org"
attribute root { xsd:string }?
ldap-server.attlist &=
## Explicitly specifies which embedded ldap server should use. Values are `apacheds` and `unboundid`. By default, it will depends if the library is available in the classpath.
attribute mode { "apacheds" | "unboundid" }?

ldap-server-ref-attribute =
## The optional server to use. If omitted, and a default LDAP server is registered (using <ldap-server> with no Id), that server will be used.
Expand Down
Loading

0 comments on commit 9b2af94

Please sign in to comment.