Skip to content

Commit 83fd4fb

Browse files
Merge pull request #39258 from PhilKes
* pr/39258: Polish "Add Docker Compose service connection support for OpenLDAP" Add Docker Compose service connection support for OpenLDAP Closes gh-39258
2 parents a0a804c + bee6fe8 commit 83fd4fb

File tree

17 files changed

+591
-8
lines changed

17 files changed

+591
-8
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfiguration.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,18 +46,25 @@
4646
@EnableConfigurationProperties(LdapProperties.class)
4747
public class LdapAutoConfiguration {
4848

49+
@Bean
50+
@ConditionalOnMissingBean(LdapConnectionDetails.class)
51+
PropertiesLdapConnectionDetails propertiesLdapConnectionDetails(LdapProperties properties,
52+
Environment environment) {
53+
return new PropertiesLdapConnectionDetails(properties, environment);
54+
}
55+
4956
@Bean
5057
@ConditionalOnMissingBean
51-
public LdapContextSource ldapContextSource(LdapProperties properties, Environment environment,
58+
public LdapContextSource ldapContextSource(LdapConnectionDetails connectionDetails, LdapProperties properties,
5259
ObjectProvider<DirContextAuthenticationStrategy> dirContextAuthenticationStrategy) {
5360
LdapContextSource source = new LdapContextSource();
5461
dirContextAuthenticationStrategy.ifUnique(source::setAuthenticationStrategy);
5562
PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
56-
propertyMapper.from(properties.getUsername()).to(source::setUserDn);
57-
propertyMapper.from(properties.getPassword()).to(source::setPassword);
63+
propertyMapper.from(connectionDetails.getUsername()).to(source::setUserDn);
64+
propertyMapper.from(connectionDetails.getPassword()).to(source::setPassword);
5865
propertyMapper.from(properties.getAnonymousReadOnly()).to(source::setAnonymousReadOnly);
59-
propertyMapper.from(properties.getBase()).to(source::setBase);
60-
propertyMapper.from(properties.determineUrls(environment)).to(source::setUrls);
66+
propertyMapper.from(connectionDetails.getBase()).to(source::setBase);
67+
propertyMapper.from(connectionDetails.getUrls()).to(source::setUrls);
6168
propertyMapper.from(properties.getBaseEnvironment())
6269
.to((baseEnvironment) -> source.setBaseEnvironmentProperties(Collections.unmodifiableMap(baseEnvironment)));
6370
return source;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.ldap;
18+
19+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
20+
21+
/**
22+
* Details required to establish a connection to an LDAP service.
23+
*
24+
* @author Philipp Kessler
25+
* @since 3.3.0
26+
*/
27+
public interface LdapConnectionDetails extends ConnectionDetails {
28+
29+
/**
30+
* LDAP URLs of the server.
31+
* @return the LDAP URLs to use
32+
*/
33+
String[] getUrls();
34+
35+
/**
36+
* Base suffix from which all operations should originate.
37+
* @return base suffix
38+
*/
39+
default String getBase() {
40+
return null;
41+
}
42+
43+
/**
44+
* Login username of the server.
45+
* @return login username
46+
*/
47+
default String getUsername() {
48+
return null;
49+
}
50+
51+
/**
52+
* Login password of the server.
53+
* @return login password
54+
*/
55+
default String getPassword() {
56+
return null;
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.ldap;
18+
19+
import org.springframework.core.env.Environment;
20+
21+
/**
22+
* Adapts {@link LdapProperties} to {@link LdapConnectionDetails}.
23+
*
24+
* @author Philipp Kessler
25+
* @since 3.3.0
26+
*/
27+
public class PropertiesLdapConnectionDetails implements LdapConnectionDetails {
28+
29+
private final LdapProperties properties;
30+
31+
private final Environment environment;
32+
33+
PropertiesLdapConnectionDetails(LdapProperties properties, Environment environment) {
34+
this.properties = properties;
35+
this.environment = environment;
36+
}
37+
38+
@Override
39+
public String[] getUrls() {
40+
return this.properties.determineUrls(this.environment);
41+
}
42+
43+
@Override
44+
public String getBase() {
45+
return this.properties.getBase();
46+
}
47+
48+
@Override
49+
public String getUsername() {
50+
return this.properties.getUsername();
51+
}
52+
53+
@Override
54+
public String getPassword() {
55+
return this.properties.getPassword();
56+
}
57+
58+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ldap/LdapAutoConfigurationTests.java

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy;
3030
import org.springframework.ldap.pool2.factory.PoolConfig;
3131
import org.springframework.ldap.pool2.factory.PooledContextSource;
32+
import org.springframework.ldap.support.LdapUtils;
3233

3334
import static org.assertj.core.api.Assertions.assertThat;
3435
import static org.mockito.Mockito.mock;
@@ -112,6 +113,25 @@ void contextSourceWithNoCustomization() {
112113
});
113114
}
114115

116+
@Test
117+
void definesPropertiesBasedConnectionDetailsByDefault() {
118+
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(PropertiesLdapConnectionDetails.class));
119+
}
120+
121+
@Test
122+
void usesCustomConnectionDetailsWhenDefined() {
123+
this.contextRunner.withUserConfiguration(ConnectionDetailsConfiguration.class).run((context) -> {
124+
assertThat(context).hasSingleBean(LdapContextSource.class)
125+
.hasSingleBean(LdapConnectionDetails.class)
126+
.doesNotHaveBean(PropertiesLdapConnectionDetails.class);
127+
LdapContextSource contextSource = context.getBean(LdapContextSource.class);
128+
assertThat(contextSource.getUrls()).isEqualTo(new String[] { "ldaps://ldap.example.com" });
129+
assertThat(contextSource.getBaseLdapName()).isEqualTo(LdapUtils.newLdapName("dc=base"));
130+
assertThat(contextSource.getUserDn()).isEqualTo("ldap-user");
131+
assertThat(contextSource.getPassword()).isEqualTo("ldap-password");
132+
});
133+
}
134+
115135
@Test
116136
void templateExists() {
117137
this.contextRunner.withPropertyValues("spring.ldap.urls:ldap://localhost:389").run((context) -> {
@@ -174,6 +194,37 @@ void contextSourceWithCustomNonUniqueDirContextAuthenticationStrategy() {
174194
});
175195
}
176196

197+
@Configuration(proxyBeanMethods = false)
198+
static class ConnectionDetailsConfiguration {
199+
200+
@Bean
201+
LdapConnectionDetails ldapConnectionDetails() {
202+
return new LdapConnectionDetails() {
203+
204+
@Override
205+
public String[] getUrls() {
206+
return new String[] { "ldaps://ldap.example.com" };
207+
}
208+
209+
@Override
210+
public String getBase() {
211+
return "dc=base";
212+
}
213+
214+
@Override
215+
public String getUsername() {
216+
return "ldap-user";
217+
}
218+
219+
@Override
220+
public String getPassword() {
221+
return "ldap-password";
222+
}
223+
};
224+
}
225+
226+
}
227+
177228
@Configuration(proxyBeanMethods = false)
178229
static class PooledContextSourceConfig {
179230

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.ldap;
18+
19+
import java.util.Arrays;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
23+
import org.springframework.boot.autoconfigure.ldap.LdapConnectionDetails;
24+
import org.springframework.boot.docker.compose.core.RunningService;
25+
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory;
26+
import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource;
27+
28+
/**
29+
* {@link DockerComposeConnectionDetailsFactory} to create {@link LdapConnectionDetails}
30+
* for an {@code ldap} service.
31+
*
32+
* @author Philipp Kessler
33+
*/
34+
class OpenLdapDockerComposeConnectionDetailsFactory
35+
extends DockerComposeConnectionDetailsFactory<LdapConnectionDetails> {
36+
37+
protected OpenLdapDockerComposeConnectionDetailsFactory() {
38+
super("osixia/openldap");
39+
}
40+
41+
@Override
42+
protected LdapConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
43+
return new OpenLdapDockerComposeConnectionDetails(source.getRunningService());
44+
}
45+
46+
/**
47+
* {@link LdapConnectionDetails} backed by an {@code openldap} {@link RunningService}.
48+
*/
49+
static class OpenLdapDockerComposeConnectionDetails extends DockerComposeConnectionDetails
50+
implements LdapConnectionDetails {
51+
52+
private final String[] urls;
53+
54+
private final String base;
55+
56+
private final String username;
57+
58+
private final String password;
59+
60+
OpenLdapDockerComposeConnectionDetails(RunningService service) {
61+
super(service);
62+
Map<String, String> env = service.env();
63+
boolean usesTls = Boolean.parseBoolean(env.getOrDefault("LDAP_TLS", "true"));
64+
String ldapPort = usesTls ? env.getOrDefault("LDAPS_PORT", "636") : env.getOrDefault("LDAP_PORT", "389");
65+
this.urls = new String[] { "%s://%s:%d".formatted(usesTls ? "ldaps" : "ldap", service.host(),
66+
service.ports().get(Integer.parseInt(ldapPort))) };
67+
if (env.containsKey("LDAP_BASE_DN")) {
68+
this.base = env.get("LDAP_BASE_DN");
69+
}
70+
else {
71+
this.base = Arrays.stream(env.getOrDefault("LDAP_DOMAIN", "example.org").split("\\."))
72+
.map("dc=%s"::formatted)
73+
.collect(Collectors.joining(","));
74+
}
75+
this.password = env.getOrDefault("LDAP_ADMIN_PASSWORD", "admin");
76+
this.username = "cn=admin,%s".formatted(this.base);
77+
}
78+
79+
@Override
80+
public String[] getUrls() {
81+
return this.urls;
82+
}
83+
84+
@Override
85+
public String getBase() {
86+
return this.base;
87+
}
88+
89+
@Override
90+
public String getUsername() {
91+
return this.username;
92+
}
93+
94+
@Override
95+
public String getPassword() {
96+
return this.password;
97+
}
98+
99+
}
100+
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for Docker Compose LDAP service connections.
19+
*/
20+
package org.springframework.boot.docker.compose.service.connection.ldap;

spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ org.springframework.boot.docker.compose.service.connection.activemq.ActiveMQDock
99
org.springframework.boot.docker.compose.service.connection.cassandra.CassandraDockerComposeConnectionDetailsFactory,\
1010
org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\
1111
org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\
12+
org.springframework.boot.docker.compose.service.connection.ldap.OpenLdapDockerComposeConnectionDetailsFactory,\
1213
org.springframework.boot.docker.compose.service.connection.liquibase.JdbcAdaptingLiquibaseConnectionDetailsFactory,\
1314
org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbJdbcDockerComposeConnectionDetailsFactory,\
1415
org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbR2dbcDockerComposeConnectionDetailsFactory,\

0 commit comments

Comments
 (0)