From 4c757e1d88c5cc7dacd5248eb87ce56dd34844e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 20 Feb 2025 16:20:06 -0600 Subject: [PATCH 1/2] Add ServiceConnection support for lldap/lldap (Compose) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- ...nectionDetailsFactoryIntegrationTests.java | 41 +++++++++ .../connection/ldap/lldap-compose.yaml | 8 ++ ...DockerComposeConnectionDetailsFactory.java | 92 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + .../pages/features/dev-services.adoc | 2 +- .../build.gradle | 1 + .../boot/testsupport/container/TestImage.java | 6 ++ 7 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/lldap-compose.yaml create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactory.java diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..eb84a441cd6d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2025 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.boot.docker.compose.service.connection.ldap; + +import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link LLdapDockerComposeConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class LLdapDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "lldap-compose.yaml", image = TestImage.LLDAP) + void runCreatesConnectionDetails(LdapConnectionDetails connectionDetails) { + assertThat(connectionDetails.getUsername()).isEqualTo("cn=admin,ou=people,dc=springframework,dc=org"); + assertThat(connectionDetails.getPassword()).isEqualTo("somepassword"); + assertThat(connectionDetails.getBase()).isEqualTo("dc=springframework,dc=org"); + assertThat(connectionDetails.getUrls()).hasSize(1); + assertThat(connectionDetails.getUrls()[0]).startsWith("ldap://"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/lldap-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/lldap-compose.yaml new file mode 100644 index 000000000000..c345b52ca2be --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/ldap/lldap-compose.yaml @@ -0,0 +1,8 @@ +services: + ldap: + image: '{imageName}' + environment: + - 'LLDAP_LDAP_BASE_DN=dc=springframework,dc=org' + - 'LLDAP_LDAP_USER_PASS=somepassword' + ports: + - "3890" diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..406efe000cba --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/ldap/LLdapDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2025 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.boot.docker.compose.service.connection.ldap; + +import java.util.Map; + +import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link LdapConnectionDetails} + * for an {@code ldap} service. + * + * @author Eddú Meléndez + */ +class LLdapDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + + protected LLdapDockerComposeConnectionDetailsFactory() { + super("lldap/lldap"); + } + + @Override + protected LdapConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new LLdapDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link LdapConnectionDetails} backed by an {@code lldap} {@link RunningService}. + */ + static class LLdapDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements LdapConnectionDetails { + + private final String[] urls; + + private final String base; + + private final String username; + + private final String password; + + LLdapDockerComposeConnectionDetails(RunningService service) { + super(service); + Map env = service.env(); + boolean usesTls = Boolean.parseBoolean(env.getOrDefault("LLDAP_LDAPS_OPTIONS__ENABLED", "false")); + String ldapPort = usesTls ? env.getOrDefault("LLDAP_LDAPS_OPTIONS__PORT", "6360") + : env.getOrDefault("LLDAP_LDAP_PORT", "3890"); + this.urls = new String[] { "%s://%s:%d".formatted(usesTls ? "ldaps" : "ldap", service.host(), + service.ports().get(Integer.parseInt(ldapPort))) }; + this.base = env.getOrDefault("LLDAP_LDAP_BASE_DN", "dc=example,dc=com"); + this.password = env.getOrDefault("LLDAP_LDAP_USER_PASS", "password"); + this.username = "cn=admin,ou=people,%s".formatted(this.base); + } + + @Override + public String[] getUrls() { + return this.urls; + } + + @Override + public String getBase() { + return this.base; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public String getPassword() { + return this.password; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index ad1a5dfc2416..d2defa268464 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -14,6 +14,7 @@ org.springframework.boot.docker.compose.service.connection.clickhouse.ClickHouse org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.hazelcast.HazelcastDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.ldap.LLdapDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.ldap.OpenLdapDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.liquibase.JdbcAdaptingLiquibaseConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbJdbcDockerComposeConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc index 94a6dc042986..af495d1af2b4 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc @@ -102,7 +102,7 @@ The following service connections are currently supported: | Containers named "clickhouse/clickhouse-server", "bitnami/clickhouse", "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "bitnami/mariadb", "mssql/server", "mysql", "bitnami/mysql", "postgres", or "bitnami/postgresql" | javadoc:org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails[] -| Containers named "osixia/openldap" +| Containers named "osixia/openldap", "lldap/lldap" | javadoc:org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails[] | Containers named "mongo" or "bitnami/mongodb" diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/build.gradle index aae510e78bc2..a14cdea6199d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/build.gradle @@ -21,6 +21,7 @@ dependencies { optional("org.testcontainers:grafana") optional("org.testcontainers:junit-jupiter") optional("org.testcontainers:kafka") + optional("org.testcontainers:ldap") optional("org.testcontainers:mongodb") optional("org.testcontainers:neo4j") optional("org.testcontainers:oracle-xe") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java index 143378709414..e3ffcbbaa1ff 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java @@ -39,6 +39,7 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer; import org.testcontainers.grafana.LgtmStackContainer; import org.testcontainers.kafka.ConfluentKafkaContainer; +import org.testcontainers.ldap.LLdapContainer; import org.testcontainers.redpanda.RedpandaContainer; import org.testcontainers.utility.DockerImageName; @@ -145,6 +146,11 @@ public enum TestImage { CONFLUENT_KAFKA_DEPRECATED("confluentinc/cp-kafka", "7.4.0", () -> org.testcontainers.containers.KafkaContainer.class), + /** + * A container image suitable for testing LLDAP. + */ + LLDAP("lldap/lldap", "v0.6.1-alpine", () -> LLdapContainer.class), + /** * A container image suitable for testing OpenLDAP. */ From 19c1bae925ceeac3aa8fa426e9040d7141875f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 20 Feb 2025 16:21:05 -0600 Subject: [PATCH 2/2] Add ServiceConnection support for LLdapContainer (Testcontainers) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- .../pages/testing/testcontainers.adoc | 3 + .../spring-boot-testcontainers/build.gradle | 1 + ...nectionDetailsFactoryIntegrationTests.java | 68 ++++++++++++++++++ ...LdapContainerConnectionDetailsFactory.java | 69 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + 5 files changed, 142 insertions(+) create mode 100644 spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactory.java diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc index d6a7d4c2e862..350794cf2f06 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/testcontainers.adoc @@ -62,6 +62,9 @@ The following service connection factories are provided in the `spring-boot-test | javadoc:org.springframework.boot.autoconfigure.kafka.KafkaConnectionDetails[] | Containers of type javadoc:org.testcontainers.kafka.KafkaContainer[], javadoc:org.testcontainers.kafka.ConfluentKafkaContainer[] or javadoc:org.testcontainers.redpanda.RedpandaContainer[] +| javadoc:org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails[] +| Containers named "osixia/openldap" or of type javadoc:org.testcontainers.ldap.LLdapContainer[] + | javadoc:org.springframework.boot.autoconfigure.liquibase.LiquibaseConnectionDetails[] | Containers of type javadoc:{url-testcontainers-jdbc-javadoc}/org.testcontainers.containers.JdbcDatabaseContainer[] diff --git a/spring-boot-project/spring-boot-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index cdbe496ae53b..73bbbcf974e6 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -70,6 +70,7 @@ dependencies { optional("org.testcontainers:grafana") optional("org.testcontainers:jdbc") optional("org.testcontainers:kafka") + optional("org.testcontainers:ldap") optional("org.testcontainers:mariadb") optional("org.testcontainers:mongodb") optional("org.testcontainers:mssqlserver") diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..3bb58c6e5738 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2025 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.boot.testcontainers.service.connection.ldap; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.ldap.LLdapContainer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.query.LdapQueryBuilder; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LLdapContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class LLdapContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final LLdapContainer lldap = TestImage.container(LLdapContainer.class); + + @Autowired + private LdapTemplate ldapTemplate; + + @Test + void connectionCanBeMadeToLdapContainer() { + List cn = this.ldapTemplate.search(LdapQueryBuilder.query().where("objectClass").is("inetOrgPerson"), + (AttributesMapper) (attributes) -> attributes.get("cn").get().toString()); + assertThat(cn).singleElement().isEqualTo("Administrator"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ LdapAutoConfiguration.class }) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..6b419a9d0921 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ldap/LLdapContainerConnectionDetailsFactory.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2025 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.boot.testcontainers.service.connection.ldap; + +import org.testcontainers.ldap.LLdapContainer; + +import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link LdapConnectionDetails} from + * a {@link ServiceConnection @ServiceConnection}-annotated {@link LLdapContainer}. + * + * @author Eddú Meléndez + */ +class LLdapContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected LdapConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { + return new LLdapContainerConnectionDetails(source); + } + + private static final class LLdapContainerConnectionDetails extends ContainerConnectionDetails + implements LdapConnectionDetails { + + private LLdapContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public String[] getUrls() { + return new String[] { getContainer().getLdapUrl() }; + } + + @Override + public String getBase() { + return getContainer().getBaseDn(); + } + + @Override + public String getUsername() { + return getContainer().getUser(); + } + + @Override + public String getPassword() { + return getContainer().getUserPass(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 902b2c43aca2..20a62beb5fbe 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -22,6 +22,7 @@ org.springframework.boot.testcontainers.service.connection.jdbc.JdbcContainerCon org.springframework.boot.testcontainers.service.connection.kafka.ApacheKafkaContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.kafka.ConfluentKafkaContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.kafka.DeprecatedConfluentKafkaContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.ldap.LLdapContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.ldap.OpenLdapContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.liquibase.LiquibaseContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.mongo.MongoContainerConnectionDetailsFactory,\