Skip to content

Commit

Permalink
Add LDAP health actuator
Browse files Browse the repository at this point in the history
Provide specific health actuator endpoint to verify if LDAP connection
is valid.

See spring-projectsgh-7905
  • Loading branch information
eddumelendez authored and snicoll committed Jan 16, 2017
1 parent db99ed8 commit b2250f4
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 5 deletions.
5 changes: 5 additions & 0 deletions spring-boot-actuator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@
<artifactId>spring-data-couchbase</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-ldap</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,7 @@
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.JmsHealthIndicator;
import org.springframework.boot.actuate.health.LdapHealthIndicator;
import org.springframework.boot.actuate.health.MailHealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
Expand All @@ -56,6 +57,7 @@
import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration;
import org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration;
Expand All @@ -77,6 +79,7 @@
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.mail.javamail.JavaMailSenderImpl;

/**
Expand All @@ -96,9 +99,10 @@
CassandraDataAutoConfiguration.class, CouchbaseDataAutoConfiguration.class,
DataSourceAutoConfiguration.class, ElasticsearchAutoConfiguration.class,
JestAutoConfiguration.class, JmsAutoConfiguration.class,
MailSenderAutoConfiguration.class, MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class, RabbitAutoConfiguration.class,
RedisAutoConfiguration.class, SolrAutoConfiguration.class })
LdapDataAutoConfiguration.class, MailSenderAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
RabbitAutoConfiguration.class, RedisAutoConfiguration.class,
SolrAutoConfiguration.class })
@EnableConfigurationProperties({ HealthIndicatorProperties.class })
@Import({
ElasticsearchHealthIndicatorConfiguration.ElasticsearchClientHealthIndicatorConfiguration.class,
Expand Down Expand Up @@ -231,6 +235,27 @@ private String getValidationQuery(DataSource source) {

}

@Configuration
@ConditionalOnClass(LdapOperations.class)
@ConditionalOnBean(LdapOperations.class)
@ConditionalOnEnabledHealthIndicator("ldap")
public static class LdapHealthIndicatorConfiguration extends
CompositeHealthIndicatorConfiguration<LdapHealthIndicator, LdapOperations> {

private final Map<String, LdapOperations> ldapOperations;

public LdapHealthIndicatorConfiguration(Map<String, LdapOperations> ldapOperations) {
this.ldapOperations = ldapOperations;
}

@Bean
@ConditionalOnMissingBean(name = "ldapHealthIndicator")
public HealthIndicator ldapHealthIndicator() {
return createHealthIndicator(this.ldapOperations);
}

}

@Configuration
@ConditionalOnBean(MongoTemplate.class)
@ConditionalOnEnabledHealthIndicator("mongo")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2012-2017 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
*
* http://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.actuate.health;

import javax.naming.NamingException;
import javax.naming.directory.DirContext;

import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.util.Assert;

/**
* {@link HealthIndicator} for configured LDAP server(s).
*
* @author Eddú Meléndez
* @version 1.5.0
*/
public class LdapHealthIndicator extends AbstractHealthIndicator {

private final LdapOperations ldapOperations;

public LdapHealthIndicator(LdapOperations ldapOperations) {
Assert.notNull(ldapOperations, "LdapOperations must not be null");
this.ldapOperations = ldapOperations;
}

@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
String version = (String) this.ldapOperations.executeReadOnly(new ContextExecutor<Object>() {
@Override
public Object executeWithContext(DirContext ctx) throws NamingException {
return ctx.getEnvironment().get("java.naming.ldap.version");
}
});
builder.up().withDetail("version", version);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@
"description": "Enable JMS health check.",
"defaultValue": true
},
{
"name": "management.health.ldap.enabled",
"type": "java.lang.Boolean",
"description": "Enable LDAP health check.",
"defaultValue": true
},
{
"name": "management.health.mongo.enabled",
"type": "java.lang.Boolean",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,7 @@
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.JmsHealthIndicator;
import org.springframework.boot.actuate.health.LdapHealthIndicator;
import org.springframework.boot.actuate.health.MailHealthIndicator;
import org.springframework.boot.actuate.health.MongoHealthIndicator;
import org.springframework.boot.actuate.health.RabbitHealthIndicator;
Expand Down Expand Up @@ -63,6 +64,7 @@
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.ldap.core.LdapOperations;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -535,6 +537,35 @@ public void notCouchbaseHealthIndicator() throws Exception {
.isEqualTo(ApplicationHealthIndicator.class);
}

@Test
public void ldapHealthIndicator() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"management.health.diskspace.enabled:false");
this.context.register(LdapConfiguration.class,
ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class);
this.context.refresh();
Map<String, HealthIndicator> beans = this.context
.getBeansOfType(HealthIndicator.class);
assertThat(beans.size()).isEqualTo(1);
assertThat(beans.values().iterator().next().getClass())
.isEqualTo(LdapHealthIndicator.class);
}

@Test
public void notLdapHealthIndicator() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"management.health.diskspace.enabled:false",
"management.health.ldap.enabled:false");
this.context.register(LdapConfiguration.class,
ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class);
this.context.refresh();
Map<String, HealthIndicator> beans = this.context
.getBeansOfType(HealthIndicator.class);
assertThat(beans.size()).isEqualTo(1);
assertThat(beans.values().iterator().next().getClass())
.isEqualTo(ApplicationHealthIndicator.class);
}

@Configuration
@EnableConfigurationProperties
protected static class DataSourceConfig {
Expand Down Expand Up @@ -605,4 +636,15 @@ public JestClient jestClient() {

}

@Configuration
protected static class LdapConfiguration {

@Bean
public LdapOperations ldapOperations() {
LdapOperations operations = mock(LdapOperations.class);
return operations;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2012-2017 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
*
* http://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.actuate.health;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.LdapTemplate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
* Tests for {@link LdapHealthIndicator}
*
* @author Eddú Meléndez
*/
public class LdapHealthIndicatorTests {

private AnnotationConfigApplicationContext context;

@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
}

@After
public void close() {
if (this.context != null) {
this.context.close();
}
}

@Test
public void indicatorExist() {
this.context.register(LdapAutoConfiguration.class, LdapDataAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, EndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class);
this.context.refresh();
LdapTemplate ldapTemplate = this.context.getBean(LdapTemplate.class);
assertThat(ldapTemplate).isNotNull();
LdapHealthIndicator healthIndicator = this.context.getBean(LdapHealthIndicator.class);
assertThat(healthIndicator).isNotNull();
}

@Test
public void ldapIsUp() {
LdapTemplate ldapTemplate = mock(LdapTemplate.class);
given(ldapTemplate.executeReadOnly(any(ContextExecutor.class))).willReturn("3");
LdapHealthIndicator healthIndicator = new LdapHealthIndicator(ldapTemplate);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails().get("version")).isEqualTo("3");
verify(ldapTemplate).executeReadOnly(any(ContextExecutor.class));
}

@Test
public void ldapIsDown() {
LdapTemplate ldapTemplate = mock(LdapTemplate.class);
given(ldapTemplate.executeReadOnly(any(ContextExecutor.class)))
.willThrow(new CommunicationException(new javax.naming.CommunicationException("Connection failed")));
LdapHealthIndicator healthIndicator = new LdapHealthIndicator(ldapTemplate);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat((String) health.getDetails().get("error"))
.contains("Connection failed");
verify(ldapTemplate).executeReadOnly(any(ContextExecutor.class));
}

}

0 comments on commit b2250f4

Please sign in to comment.