1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Remove ApacheDS Support

Closes gh-13852
This commit is contained in:
Josh Cummings
2025-06-18 18:29:16 -06:00
parent 42e24aa53c
commit 6ddb964c61
49 changed files with 138 additions and 1406 deletions
-6
View File
@@ -15,12 +15,6 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'ldapsdk:ldapsdk'
optional "com.unboundid:unboundid-ldapsdk"
optional "org.apache.directory.server:apacheds-core"
optional "org.apache.directory.server:apacheds-core-entry"
optional "org.apache.directory.server:apacheds-protocol-shared"
optional "org.apache.directory.server:apacheds-protocol-ldap"
optional "org.apache.directory.server:apacheds-server-jndi"
optional 'org.apache.directory.shared:shared-ldap'
api ('org.springframework.ldap:spring-ldap-core') {
exclude(group: 'commons-logging', module: 'commons-logging')
exclude(group: 'org.springframework', module: 'spring-beans')
@@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class DefaultSpringSecurityContextSourceTests {
@Autowired
@@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class SpringSecurityLdapTemplateITests {
@Autowired
@@ -116,10 +116,10 @@ public class SpringSecurityLdapTemplateITests {
assertThat(values).hasSize(1);
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "objectclass", "top", "person", "organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "objectClass", "top", "person", "organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertThat(record.containsKey("userPassword")).isFalse();
assertThat(record.containsKey("userPassword")).isTrue();
}
@Test
@@ -129,10 +129,10 @@ public class SpringSecurityLdapTemplateITests {
assertThat(values).hasSize(1);
Map<String, List<String>> record = values.iterator().next();
assertAttributeValue(record, "uid", "bob");
assertAttributeValue(record, "objectclass", "top", "person", "organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "objectClass", "top", "person", "organizationalPerson", "inetOrgPerson");
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertThat(record.containsKey("userPassword")).isFalse();
assertThat(record.containsKey("userPassword")).isTrue();
}
@Test
@@ -145,7 +145,7 @@ public class SpringSecurityLdapTemplateITests {
assertAttributeValue(record, "cn", "Bob Hamilton");
assertAttributeValue(record, "sn", "Hamilton");
assertThat(record.containsKey("userPassword")).isFalse();
assertThat(record.containsKey("objectclass")).isFalse();
assertThat(record.containsKey("objectClass")).isFalse();
}
protected void assertAttributeValue(Map<String, List<String>> record, String attributeName, String... values) {
@@ -20,31 +20,31 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer;
/**
* @author Eddú Meléndez
*/
@Configuration
public class ApacheDsContainerConfig implements DisposableBean {
public class UnboundIdContainerConfig implements DisposableBean {
private ApacheDSContainer container;
private UnboundIdContainer container;
@Bean
ApacheDSContainer ldapContainer() throws Exception {
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
UnboundIdContainer ldapContainer() {
this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
this.container.setPort(0);
return this.container;
}
@Bean
ContextSource contextSource(ApacheDSContainer ldapContainer) throws Exception {
ContextSource contextSource(UnboundIdContainer ldapContainer) {
return new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=springframework,dc=org");
"ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=springframework,dc=org");
}
@Override
public void destroy() throws Exception {
public void destroy() {
this.container.stop();
}
@@ -33,8 +33,8 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.ldap.ApacheDsContainerConfig;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.UnboundIdContainerConfig;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -53,7 +53,7 @@ import static org.mockito.Mockito.spy;
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class BindAuthenticatorTests {
@Autowired
@@ -30,8 +30,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.ldap.ApacheDsContainerConfig;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.UnboundIdContainerConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class PasswordComparisonAuthenticatorTests {
@Autowired
@@ -25,8 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.ApacheDsContainerConfig;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.UnboundIdContainerConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class FilterBasedLdapUserSearchTests {
@Autowired
@@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Steve Riesenberg
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.ApacheDsContainerWithSpacesConfig.class)
@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.UnboundIdContainerWithSpacesConfig.class)
public class FilterBasedLdapUserSearchWithSpacesTests {
@Autowired
@@ -61,22 +61,22 @@ public class FilterBasedLdapUserSearchWithSpacesTests {
}
@Configuration
static class ApacheDsContainerWithSpacesConfig implements DisposableBean {
static class UnboundIdContainerWithSpacesConfig implements DisposableBean {
private ApacheDSContainer container;
private UnboundIdContainer container;
@Bean
ApacheDSContainer ldapContainer() throws Exception {
this.container = new ApacheDSContainer("dc=spring framework,dc=org",
UnboundIdContainer ldapContainer() {
this.container = new UnboundIdContainer("dc=spring framework,dc=org",
"classpath:test-server-with-spaces.ldif");
this.container.setPort(0);
return this.container;
}
@Bean
ContextSource contextSource(ApacheDSContainer ldapContainer) {
ContextSource contextSource(UnboundIdContainer ldapContainer) {
return new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=spring%20framework,dc=org");
"ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=spring%20framework,dc=org");
}
@Override
@@ -1,221 +0,0 @@
/*
* 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.
* 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.ldap.server;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.fail;
/**
* Useful for debugging the container by itself.
*
* @author Luke Taylor
* @author Rob Winch
* @author Gunnar Hillert
* @author Evgeniy Cheban
* @since 3.0
*/
public class ApacheDSContainerTests {
@TempDir
public File temporaryFolder;
// SEC-2162
@Test
public void failsToStartThrowsException() throws Exception {
ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:missing.ldif");
List<Integer> ports = getDefaultPorts(1);
server1.setPort(ports.get(0));
server2.setPort(ports.get(0));
try {
server1.afterPropertiesSet();
try {
server2.afterPropertiesSet();
fail("Expected Exception");
}
catch (RuntimeException success) {
}
}
finally {
try {
server1.destroy();
}
catch (Throwable ex) {
}
try {
server2.destroy();
}
catch (Throwable ex) {
}
}
}
// SEC-2161
@Test
public void multipleInstancesSimultanciously() throws Exception {
ApacheDSContainer server1 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
ApacheDSContainer server2 = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
List<Integer> ports = getDefaultPorts(2);
server1.setPort(ports.get(0));
server2.setPort(ports.get(1));
try {
server1.afterPropertiesSet();
server2.afterPropertiesSet();
}
finally {
try {
server1.destroy();
}
catch (Throwable ex) {
}
try {
server2.destroy();
}
catch (Throwable ex) {
}
}
}
@Test
public void startWithLdapOverSslWithoutCertificate() throws Exception {
ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
List<Integer> ports = getDefaultPorts(1);
server.setPort(ports.get(0));
server.setLdapOverSslEnabled(true);
assertThatIllegalArgumentException().isThrownBy(server::afterPropertiesSet)
.withMessage("When LdapOverSsl is enabled, the keyStoreFile property must be set.");
}
@Test
@DisabledOnOs(OS.WINDOWS)
public void startWithLdapOverSslWithWrongPassword() throws Exception {
final ClassPathResource keyStoreResource = new ClassPathResource(
"/org/springframework/security/ldap/server/spring.keystore");
final File temporaryKeyStoreFile = new File(this.temporaryFolder, "spring.keystore");
FileCopyUtils.copy(keyStoreResource.getInputStream(), new FileOutputStream(temporaryKeyStoreFile));
assertThat(temporaryKeyStoreFile).isFile();
ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
List<Integer> ports = getDefaultPorts(1);
server.setPort(ports.get(0));
server.setLdapOverSslEnabled(true);
server.setKeyStoreFile(temporaryKeyStoreFile);
server.setCertificatePassord("incorrect-password");
assertThatExceptionOfType(RuntimeException.class).isThrownBy(server::afterPropertiesSet)
.withMessage("Server startup failed")
.withRootCauseInstanceOf(UnrecoverableKeyException.class);
}
/**
* This test starts an LDAP server using LDAPs (LDAP over SSL). A self-signed
* certificate is being used, which was previously generated with:
*
* <pre>
* {@code
* keytool -genkey -alias spring -keyalg RSA -keystore spring.keystore -validity 3650 -storetype JKS \
* -dname "CN=localhost, OU=Spring, O=Pivotal, L=Kailua-Kona, ST=HI, C=US" -keypass spring -storepass spring
* }
* </pre>
* @throws Exception
*/
@Test
@DisabledOnOs(OS.WINDOWS)
public void startWithLdapOverSsl() throws Exception {
final ClassPathResource keyStoreResource = new ClassPathResource(
"/org/springframework/security/ldap/server/spring.keystore");
final File temporaryKeyStoreFile = new File(this.temporaryFolder, "spring.keystore");
FileCopyUtils.copy(keyStoreResource.getInputStream(), new FileOutputStream(temporaryKeyStoreFile));
assertThat(temporaryKeyStoreFile).isFile();
ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif");
List<Integer> ports = getDefaultPorts(1);
server.setPort(ports.get(0));
server.setLdapOverSslEnabled(true);
server.setKeyStoreFile(temporaryKeyStoreFile);
server.setCertificatePassord("spring");
try {
server.afterPropertiesSet();
}
finally {
try {
server.destroy();
}
catch (Throwable ex) {
}
}
}
private List<Integer> getDefaultPorts(int count) throws IOException {
List<ServerSocket> connections = new ArrayList<>();
List<Integer> availablePorts = new ArrayList<>(count);
try {
for (int i = 0; i < count; i++) {
ServerSocket socket = new ServerSocket(0);
connections.add(socket);
availablePorts.add(socket.getLocalPort());
}
return availablePorts;
}
finally {
for (ServerSocket conn : connections) {
conn.close();
}
}
}
@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();
}
}
}
@@ -1,79 +0,0 @@
/*
* Copyright 2002-2013 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.ldap.server;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests reproducing problems with loading structures from ldif on embedded ApacheDS
* server.
*
* @author Marcin Zajączkowski
*/
public class ApacheDSEmbeddedLdifTests {
private static final String LDAP_ROOT = "ou=ssattributes,dc=springframework,dc=org";
private static final int LDAP_PORT = 52389;
private ApacheDSContainer server;
private SpringSecurityLdapTemplate ldapTemplate;
@BeforeEach
public void setUp() throws Exception {
// TODO: InMemoryXmlApplicationContext would be useful here, but it is not visible
this.server = new ApacheDSContainer(LDAP_ROOT, "classpath:test-server-custom-attribute-types.ldif");
this.server.setPort(LDAP_PORT);
this.server.afterPropertiesSet();
this.ldapTemplate = new SpringSecurityLdapTemplate(createLdapContextSource());
}
private LdapContextSource createLdapContextSource() {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl("ldap://localhost:" + LDAP_PORT);
ldapContextSource.setBase(LDAP_ROOT);
ldapContextSource.afterPropertiesSet();
return ldapContextSource;
}
@AfterEach
public void tearDown() throws Exception {
if (this.server != null) {
this.server.destroy();
}
}
@Disabled // Not fixed yet
@Test // SEC-2387
public void customAttributeTypesShouldBeProperlyCreatedWhenLoadedFromLdif() {
assertThat(this.ldapTemplate.compare("uid=objectWithCustomAttribute1", "uid", "objectWithCustomAttribute1"))
.isTrue();
assertThat(this.ldapTemplate.compare("uid=objectWithCustomAttribute1", "customAttribute", "I am custom"))
.isTrue();
}
}
@@ -31,7 +31,7 @@ import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -42,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
classes = DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.ApacheDsContainerWithUndefinedGroupRoleAttributeConfig.class)
classes = DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.UnboundIdContainerWithUndefinedGroupRoleAttributeConfig.class)
public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests {
@Autowired
@@ -77,22 +77,22 @@ public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests {
}
@Configuration
static class ApacheDsContainerWithUndefinedGroupRoleAttributeConfig implements DisposableBean {
static class UnboundIdContainerWithUndefinedGroupRoleAttributeConfig implements DisposableBean {
private ApacheDSContainer container;
private UnboundIdContainer container;
@Bean
ApacheDSContainer ldapContainer() throws Exception {
this.container = new ApacheDSContainer("dc=springframework,dc=org",
UnboundIdContainer ldapContainer() {
this.container = new UnboundIdContainer("dc=springframework,dc=org",
"classpath:test-server-with-undefined-group-role-attributes.ldif");
this.container.setPort(0);
return this.container;
}
@Bean
ContextSource contextSource(ApacheDSContainer ldapContainer) {
ContextSource contextSource(UnboundIdContainer ldapContainer) {
return new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=springframework,dc=org");
"ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=springframework,dc=org");
}
@Override
@@ -31,8 +31,8 @@ import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.ldap.ApacheDsContainerConfig;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.UnboundIdContainerConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -44,7 +44,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
@SuppressWarnings({ "deprecation" })
public class DefaultLdapAuthoritiesPopulatorTests {
@@ -34,9 +34,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.ApacheDsContainerConfig;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.UnboundIdContainerConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -52,7 +52,7 @@ import static org.mockito.Mockito.verify;
* @author Roman Zabaluev
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class LdapUserDetailsManagerTests {
@Autowired
@@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.ApacheDsContainerConfig;
import org.springframework.security.ldap.UnboundIdContainerConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Eddú Meléndez
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApacheDsContainerConfig.class)
@ContextConfiguration(classes = UnboundIdContainerConfig.class)
public class NestedLdapAuthoritiesPopulatorTests {
@Autowired
@@ -1,365 +0,0 @@
/*
* Copyright 2002-2022 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.ldap.server;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.directory.server.core.DefaultDirectoryService;
import org.apache.directory.server.core.authn.AuthenticationInterceptor;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.exception.ExceptionInterceptor;
import org.apache.directory.server.core.interceptor.Interceptor;
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.core.referral.ReferralInterceptor;
import org.apache.directory.server.core.subtree.SubentryInterceptor;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
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;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
/**
* Provides lifecycle services for the embedded apacheDS server defined by the supplied
* configuration. Used by {@code LdapServerBeanDefinitionParser}. An instance will be
* stored in the application context for each embedded server instance. It will start the
* server when the context is initialized and shut it down when it is closed. It is
* intended for temporary embedded use and will not retain changes across start/stop
* boundaries. The working directory is deleted on shutdown.
*
* <p>
* If used repeatedly in a single JVM process with the same configuration (for example,
* when repeatedly loading an application context during testing), it's important that the
* application context is closed to allow the bean to be disposed of and the server
* shutdown prior to attempting to start it again.
* <p>
* This class is intended for testing and internal security namespace use, only, and is
* not considered part of the framework's public API.
*
* @author Luke Taylor
* @author Rob Winch
* @author Gunnar Hillert
* @author Evgeniy Cheban
* @deprecated For removal in 7.0. Use {@link UnboundIdContainer} instead because ApacheDS
* 1.x is no longer supported with no GA version to replace it.
*/
@Deprecated(since = "5.2", forRemoval = true)
public class ApacheDSContainer
implements EmbeddedLdapServerContainer, InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
final DefaultDirectoryService service;
LdapServer server;
private TcpTransport transport;
private ApplicationContext ctxt;
private File workingDir;
private boolean running;
private final String ldifResources;
private final JdbmPartition partition;
private final String root;
private int port = 53389;
private int localPort;
private boolean ldapOverSslEnabled;
private File keyStoreFile;
private String certificatePassord;
public ApacheDSContainer(String root, String ldifs) throws Exception {
this.ldifResources = ldifs;
this.service = new DefaultDirectoryService();
List<Interceptor> list = new ArrayList<>();
list.add(new NormalizationInterceptor());
list.add(new AuthenticationInterceptor());
list.add(new ReferralInterceptor());
list.add(new ExceptionInterceptor());
list.add(new OperationalAttributeInterceptor());
list.add(new SubentryInterceptor());
this.service.setInterceptors(list);
this.partition = new JdbmPartition();
this.partition.setId("rootPartition");
this.partition.setSuffix(root);
this.root = root;
this.service.addPartition(this.partition);
this.service.setExitVmOnShutdown(false);
this.service.setShutdownHookEnabled(false);
this.service.getChangeLog().setEnabled(false);
this.service.setDenormalizeOpAttrsEnabled(true);
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.workingDir == null) {
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
if (apacheWorkDir == null) {
apacheWorkDir = createTempDirectory("apacheds-spring-security-");
}
setWorkingDirectory(new File(apacheWorkDir));
}
Assert.isTrue(!this.ldapOverSslEnabled || this.keyStoreFile != null,
"When LdapOverSsl is enabled, the keyStoreFile property must be set.");
this.server = new LdapServer();
this.server.setDirectoryService(this.service);
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here
this.transport = new TcpTransport(this.port);
if (this.ldapOverSslEnabled) {
this.transport.setEnableSSL(true);
this.server.setKeystoreFile(this.keyStoreFile.getAbsolutePath());
this.server.setCertificatePassword(this.certificatePassord);
}
this.server.setTransports(this.transport);
start();
}
@Override
public void destroy() {
stop();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctxt = applicationContext;
}
public void setWorkingDirectory(File workingDir) {
Assert.notNull(workingDir, "workingDir cannot be null");
this.logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath());
Assert.isTrue(!workingDir.exists(),
"The specified working directory '" + workingDir.getAbsolutePath()
+ "' already exists. Another directory service instance may be using it or it may be from a "
+ " previous unclean shutdown. Please confirm and delete it or configure a different "
+ "working directory");
this.workingDir = workingDir;
this.service.setWorkingDirectory(workingDir);
}
@Override
public void setPort(int port) {
this.port = port;
}
@Override
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.
* @param ldapOverSslEnabled If not set, will default to false
*/
public void setLdapOverSslEnabled(boolean ldapOverSslEnabled) {
this.ldapOverSslEnabled = ldapOverSslEnabled;
}
/**
* The keyStore must not be null and must be a valid file. Will set the keyStore file
* on the underlying {@link LdapServer}.
* @param keyStoreFile Mandatory if LDAPs is enabled
*/
public void setKeyStoreFile(File keyStoreFile) {
Assert.notNull(keyStoreFile, "The keyStoreFile must not be null.");
Assert.isTrue(keyStoreFile.isFile(), "The keyStoreFile must be a file.");
this.keyStoreFile = keyStoreFile;
}
/**
* Will set the certificate password on the underlying {@link LdapServer}.
* @param certificatePassord May be null
*/
public void setCertificatePassord(String certificatePassord) {
this.certificatePassord = certificatePassord;
}
public DefaultDirectoryService getService() {
return this.service;
}
@Override
public void start() {
if (isRunning()) {
return;
}
Assert.state(!this.service.isStarted(), "DirectoryService is already running.");
this.logger.info("Starting directory server...");
try {
this.service.startup();
this.server.start();
}
catch (Exception ex) {
throw new RuntimeException("Server startup failed", ex);
}
try {
this.service.getAdminSession().lookup(this.partition.getSuffixDn());
}
catch (LdapNameNotFoundException ex) {
handleLdapNameNotFoundException();
}
catch (Exception ex) {
this.logger.error("Lookup failed", ex);
}
SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport);
InetSocketAddress localAddress = socketAcceptor.getLocalAddress();
this.localPort = localAddress.getPort();
this.running = true;
try {
importLdifs();
}
catch (Exception ex) {
throw new RuntimeException("Failed to import LDIF file(s)", ex);
}
}
private void handleLdapNameNotFoundException() {
try {
LdapDN dn = new LdapDN(this.root);
Assert.isTrue(this.root.startsWith("dc="), "root must start with dc=");
String dc = this.root.substring(3, this.root.indexOf(','));
ServerEntry entry = this.service.newEntry(dn);
entry.add("objectClass", "top", "domain", "extensibleObject");
entry.add("dc", dc);
this.service.getAdminSession().add(entry);
}
catch (Exception ex) {
this.logger.error("Failed to create dc entry", ex);
}
}
@Override
public void stop() {
if (!isRunning()) {
return;
}
this.logger.info("Shutting down directory server ...");
try {
this.server.stop();
this.service.shutdown();
}
catch (Exception ex) {
this.logger.error("Shutdown failed", ex);
return;
}
this.running = false;
if (this.workingDir.exists()) {
this.logger.info("Deleting working directory " + this.workingDir.getAbsolutePath());
deleteDir(this.workingDir);
}
}
private void importLdifs() throws Exception {
// Import any ldif files
Resource[] ldifs = (this.ctxt != null) ? this.ctxt.getResources(this.ldifResources)
: new PathMatchingResourcePatternResolver().getResources(this.ldifResources);
// Note that we can't just import using the ServerContext returned
// from starting Apache DS, apparently because of the long-running issue
// DIRSERVER-169.
// We need a standard context.
// DirContext dirContext = contextSource.getReadWriteContext();
if (ldifs == null || ldifs.length == 0) {
return;
}
Assert.isTrue(ldifs.length == 1, () -> "More than one LDIF resource found with the supplied pattern:"
+ this.ldifResources + " Got " + Arrays.toString(ldifs));
String ldifFile = getLdifFile(ldifs);
this.logger.info("Loading LDIF file: " + ldifFile);
LdifFileLoader loader = new LdifFileLoader(this.service.getAdminSession(), new File(ldifFile), null,
getClass().getClassLoader());
loader.execute();
}
private String getLdifFile(Resource[] ldifs) throws IOException {
try {
return ldifs[0].getFile().getAbsolutePath();
}
catch (IOException ex) {
return ldifs[0].getURI().toString();
}
}
private String createTempDirectory(String prefix) throws IOException {
String parentTempDir = System.getProperty("java.io.tmpdir");
String fileNamePrefix = prefix + System.nanoTime();
String fileName = fileNamePrefix;
for (int i = 0; i < 1000; i++) {
File tempDir = new File(parentTempDir, fileName);
if (!tempDir.exists()) {
return tempDir.getAbsolutePath();
}
fileName = fileNamePrefix + "~" + i;
}
throw new IOException(
"Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix));
}
private boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (String child : children) {
boolean success = deleteDir(new File(dir, child));
if (!success) {
return false;
}
}
}
return dir.delete();
}
@Override
public boolean isRunning() {
return this.running;
}
}
@@ -15,7 +15,6 @@
*/
/**
* Embedded Apache Directory Server implementation, as used by the configuration
* namespace.
* Embedded UnboundID Server implementation, as used by the configuration namespace.
*/
package org.springframework.security.ldap.server;
@@ -1,173 +0,0 @@
/*
* Copyright 2002-2024 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.apache.directory.server.core.avltree;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Comparator;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to serialize the Array data.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
@SuppressWarnings("unchecked")
public class ArrayMarshaller<E> implements Marshaller<ArrayTree<E>> {
/** static logger */
private static final Logger LOG = LoggerFactory.getLogger(ArrayMarshaller.class);
/** used for serialized form of an empty AvlTree */
private static final byte[] EMPTY_TREE = new byte[1];
/** marshaller to be used for marshalling the keys */
private Marshaller<E> keyMarshaller;
/** key Comparator for the AvlTree */
private Comparator<E> comparator;
/**
* Creates a new instance of AvlTreeMarshaller with a custom key Marshaller.
* @param comparator Comparator to be used for key comparision
* @param keyMarshaller marshaller for keys
*/
public ArrayMarshaller(Comparator<E> comparator, Marshaller<E> keyMarshaller) {
this.comparator = comparator;
this.keyMarshaller = keyMarshaller;
}
/**
* Creates a new instance of AvlTreeMarshaller with the default key Marshaller which
* uses Java Serialization.
* @param comparator Comparator to be used for key comparision
*/
public ArrayMarshaller(Comparator<E> comparator) {
this.comparator = comparator;
this.keyMarshaller = DefaultMarshaller.INSTANCE;
}
/**
* Marshals the given tree to bytes
* @param tree the tree to be marshalled
*/
public byte[] serialize(ArrayTree<E> tree) {
if ((tree == null) || tree.isEmpty()) {
return EMPTY_TREE;
}
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(byteStream);
byte[] data = null;
try {
out.writeByte(0); // represents the start of an Array byte stream
out.writeInt(tree.size());
for (int position = 0; position < tree.size(); position++) {
E value = tree.get(position);
byte[] bytes = this.keyMarshaller.serialize(value);
// Write the key length
out.writeInt(bytes.length);
// Write the key if its length is not null
if (bytes.length != 0) {
out.write(bytes);
}
}
out.flush();
data = byteStream.toByteArray();
// Try to deserialize, just to see
try {
deserialize(data);
}
catch (NullPointerException npe) {
System.out.println("Bad serialization, tree : [" + StringTools.dumpBytes(data) + "]");
throw npe;
}
out.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
return data;
}
/**
* Creates an Array from given bytes of data.
* @param data byte array to be converted into an array
*/
public ArrayTree<E> deserialize(byte[] data) throws IOException {
try {
if ((data == null) || (data.length == 0)) {
throw new IOException("Null or empty data array is invalid.");
}
if ((data.length == 1) && (data[0] == 0)) {
E[] array = (E[]) new Object[] {};
ArrayTree<E> tree = new ArrayTree<E>(this.comparator, array);
return tree;
}
ByteArrayInputStream bin = new ByteArrayInputStream(data);
DataInputStream din = new DataInputStream(bin);
byte startByte = din.readByte();
if (startByte != 0) {
throw new IOException("wrong array serialized data format");
}
int size = din.readInt();
E[] nodes = (E[]) new Object[size];
for (int i = 0; i < size; i++) {
// Read the object's size
int dataSize = din.readInt();
if (dataSize != 0) {
byte[] bytes = new byte[dataSize];
din.read(bytes);
E key = this.keyMarshaller.deserialize(bytes);
nodes[i] = key;
}
}
ArrayTree<E> arrayTree = new ArrayTree<E>(this.comparator, nodes);
return arrayTree;
}
catch (NullPointerException npe) {
System.out.println("Bad tree : [" + StringTools.dumpBytes(data) + "]");
throw npe;
}
}
}
@@ -29,7 +29,6 @@ import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.apache.directory.shared.ldap.util.EmptyEnumeration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -175,7 +174,7 @@ public class ActiveDirectoryLdapAuthenticationProviderTests {
@Test
public void noUserSearchCausesUsernameNotFound() throws Exception {
given(this.ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class)))
.willReturn(new EmptyEnumeration<>());
.willReturn(new MockNamingEnumeration(null));
this.provider.contextFactory = createContextFactoryReturning(this.ctx);
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(this.joe));
}