diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle index 580a2bdbc2..ede301e05b 100644 --- a/config/spring-security-config.gradle +++ b/config/spring-security-config.gradle @@ -71,6 +71,7 @@ dependencies { testRuntime 'cglib:cglib-nodep' testRuntime 'org.hsqldb:hsqldb' + testCompile "com.unboundid:unboundid-ldapsdk" } diff --git a/config/src/main/java/org/springframework/security/config/BeanIds.java b/config/src/main/java/org/springframework/security/config/BeanIds.java index 5b9cd0109f..85027d2c73 100644 --- a/config/src/main/java/org/springframework/security/config/BeanIds.java +++ b/config/src/main/java/org/springframework/security/config/BeanIds.java @@ -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"; diff --git a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java index bb6a5dd3fd..02ff0a4505 100644 --- a/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java +++ b/config/src/main/java/org/springframework/security/config/SecurityNamespaceHandler.java @@ -86,7 +86,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { 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); @@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler { 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.*"); } diff --git a/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java index 9111af1654..f1f1342714 100644 --- a/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java @@ -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. @@ -17,9 +17,13 @@ package org.springframework.security.config.ldap; 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; @@ -28,11 +32,14 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser; 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"; @@ -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 embeddedServers; + + public LdapServerBeanDefinitionParser() { + Map 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); @@ -114,6 +137,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { * @return the BeanDefinition for the ContextSource for the embedded server. * * @see ApacheDSContainer + * @see UnboundIdContainer */ private RootBeanDefinition createEmbeddedServer(Element element, ParserContext parserContext) { @@ -142,34 +166,78 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { 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 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 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 { @@ -196,4 +264,31 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { } } } + + 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; + } + } } diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.2.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.2.rnc index 1e82f6acbd..f35432862e 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.2.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.2.rnc @@ -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 with no Id), that server will be used. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.2.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.2.xsd index 410ddc14c6..7e0e81d244 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.2.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.2.xsd @@ -124,7 +124,7 @@ - + @@ -222,6 +222,19 @@ + + + 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. + + + + + + + + + @@ -395,7 +408,7 @@ - + @@ -475,7 +488,7 @@ - + @@ -528,7 +541,7 @@ - + @@ -772,13 +785,13 @@ - - - - - - - + + + + + + + @@ -1220,7 +1233,7 @@ - + @@ -1245,7 +1258,7 @@ - + @@ -1302,7 +1315,7 @@ - + @@ -1349,7 +1362,7 @@ - + @@ -1437,7 +1450,7 @@ - + Sets up an attribute exchange configuration to request specified attributes from the @@ -1636,7 +1649,7 @@ - + @@ -1652,7 +1665,7 @@ - + @@ -1708,7 +1721,7 @@ - + @@ -1755,7 +1768,7 @@ - + @@ -1853,7 +1866,7 @@ - + @@ -1886,8 +1899,8 @@ - - + + @@ -1904,7 +1917,7 @@ - + @@ -2041,7 +2054,7 @@ - + @@ -2093,7 +2106,7 @@ - + @@ -2730,4 +2743,4 @@ - \ No newline at end of file + diff --git a/config/src/test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTest.java b/config/src/test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTest.java new file mode 100644 index 0000000000..9c5d3eefba --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParserTest.java @@ -0,0 +1,67 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.ldap; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.util.InMemoryXmlApplicationContext; +import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.UnboundIdContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Eddú Meléndez + */ +public class LdapServerBeanDefinitionParserTest { + + private InMemoryXmlApplicationContext context; + + @After + public void closeAppContext() { + if (this.context != null) { + this.context.close(); + this.context = null; + } + } + + @Test + public void apacheDirectoryServerIsStartedByDefault() { + this.context = new InMemoryXmlApplicationContext("", "5.2", null); + String[] beanNames = this.context.getBeanNamesForType(ApacheDSContainer.class); + assertThat(beanNames).hasSize(1); + assertThat(beanNames[0]).isEqualTo(BeanIds.EMBEDDED_APACHE_DS); + } + + @Test + public void apacheDirectoryServerIsStartedWhenIsSet() { + this.context = new InMemoryXmlApplicationContext("", "5.2", null); + String[] beanNames = this.context.getBeanNamesForType(ApacheDSContainer.class); + assertThat(beanNames).hasSize(1); + assertThat(beanNames[0]).isEqualTo(BeanIds.EMBEDDED_APACHE_DS); + } + + @Test + public void unboundidIsStartedWhenModeIsSet() { + this.context = new InMemoryXmlApplicationContext("", "5.2", null); + String[] beanNames = this.context.getBeanNamesForType(UnboundIdContainer.class); + assertThat(beanNames).hasSize(1); + assertThat(beanNames[0]).isEqualTo(BeanIds.EMBEDDED_UNBOUNDID); + } +} diff --git a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java index 4336af0cc8..4b7ac18d8a 100644 --- a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java +++ b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2013 the original author or authors. + * Copyright 2009-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. @@ -23,6 +23,7 @@ import org.springframework.security.util.InMemoryResource; /** * @author Luke Taylor + * @author Eddú Meléndez */ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext { static final String BEANS_OPENING = " ---- +NOTE: `spring-security` provides integration with `apacheds` and `unboundid` as a embedded ldap servers. You can choose between them using the attribute `mode` in `ldap-server`. + ==== Using an Embedded Test Server The `` element can also be used to create an embedded server, which can be very useful for testing and demonstrations. In this case you use it without the `url` attribute: diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc index 67c96202c5..88fa302c0e 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc @@ -2360,6 +2360,9 @@ This is actually the bean `id` of the `ContextSource` instance, if you want to u [[nsa-ldap-server-attributes]] ===== Attributes +[[nsa-ldap-server-mode]] +* **mode** +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. [[nsa-ldap-server-id]] * **id** diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 237c691546..9ffe2c96e1 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -1,5 +1,5 @@ = Spring Security Reference -Ben Alex; Luke Taylor; Rob Winch; Gunnar Hillert; Joe Grandja; Jay Bryant +Ben Alex; Luke Taylor; Rob Winch; Gunnar Hillert; Joe Grandja; Jay Bryant; Eddú Meléndez :include-dir: _includes :security-api-url: https://docs.spring.io/spring-security/site/docs/current/api/ :source-indent: 0