Commit af612782 authored by Madhura Bhave's avatar Madhura Bhave

Extend HealthMvcEndpoint for Cloud Foundry

The CloudFoundryHealthMvcEndpoint does not perform additional
security checks since security is handled by the interceptor.

See gh-7108
parent a3bcb277
...@@ -49,7 +49,7 @@ enum AccessLevel { ...@@ -49,7 +49,7 @@ enum AccessLevel {
/** /**
* Returns if the access level should allow access to the specified endpoint path. * Returns if the access level should allow access to the specified endpoint path.
* @param endpointPath the endpoitn path * @param endpointPath the endpoint path
* @return {@code true} if access is allowed * @return {@code true} if access is allowed
*/ */
public boolean isAccessAllowed(String endpointPath) { public boolean isAccessAllowed(String endpointPath) {
......
...@@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletRequest; ...@@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
...@@ -54,10 +55,20 @@ class CloudFoundryEndpointHandlerMapping ...@@ -54,10 +55,20 @@ class CloudFoundryEndpointHandlerMapping
protected void postProcessEndpoints(Set<NamedMvcEndpoint> endpoints) { protected void postProcessEndpoints(Set<NamedMvcEndpoint> endpoints) {
super.postProcessEndpoints(endpoints); super.postProcessEndpoints(endpoints);
Iterator<NamedMvcEndpoint> iterator = endpoints.iterator(); Iterator<NamedMvcEndpoint> iterator = endpoints.iterator();
HealthMvcEndpoint healthMvcEndpoint = null;
while (iterator.hasNext()) { while (iterator.hasNext()) {
if (iterator.next() instanceof HalJsonMvcEndpoint) { NamedMvcEndpoint endpoint = iterator.next();
if (endpoint instanceof HalJsonMvcEndpoint) {
iterator.remove(); iterator.remove();
} }
else if (endpoint instanceof HealthMvcEndpoint) {
iterator.remove();
healthMvcEndpoint = (HealthMvcEndpoint) endpoint;
}
}
if (healthMvcEndpoint != null) {
endpoints.add(
new CloudFoundryHealthMvcEndpoint(healthMvcEndpoint.getDelegate()));
} }
} }
......
/*
* Copyright 2012-2016 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.cloudfoundry;
import java.security.Principal;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
/**
* Extension of {@link HealthMvcEndpoint} for Cloud Foundry. Since security for Cloud
* Foundry actuators is already handled by the {@link CloudFoundrySecurityInterceptor},
* this endpoint skips the additional security checks done by the regular
* {@link HealthMvcEndpoint}.
*
* @author Madhura Bhave
*/
class CloudFoundryHealthMvcEndpoint extends HealthMvcEndpoint {
CloudFoundryHealthMvcEndpoint(HealthEndpoint delegate) {
super(delegate);
}
@Override
protected boolean exposeHealthDetails(Principal principal) {
return true;
}
}
...@@ -178,7 +178,7 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint ...@@ -178,7 +178,7 @@ public class HealthMvcEndpoint extends AbstractEndpointMvcAdapter<HealthEndpoint
return (accessTime - this.lastAccess) >= getDelegate().getTimeToLive(); return (accessTime - this.lastAccess) >= getDelegate().getTimeToLive();
} }
private boolean exposeHealthDetails(Principal principal) { protected boolean exposeHealthDetails(Principal principal) {
return isSecure(principal) || isUnrestricted(); return isSecure(principal) || isUnrestricted();
} }
......
...@@ -22,11 +22,15 @@ import org.junit.Test; ...@@ -22,11 +22,15 @@ import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMappingTests; import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMappingTests;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext; import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext;
import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
...@@ -88,6 +92,23 @@ public class CloudFoundryEndpointHandlerMappingTests ...@@ -88,6 +92,23 @@ public class CloudFoundryEndpointHandlerMappingTests
.isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class); .isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class);
} }
@Test
public void registersCloudFoundryHealthEndpoint() throws Exception {
StaticApplicationContext context = new StaticApplicationContext();
HealthEndpoint delegate = new HealthEndpoint(new OrderedHealthAggregator(),
Collections.<String, HealthIndicator>emptyMap());
CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping(
Collections.singleton(new TestHealthMvcEndpoint(delegate)), null, null);
handlerMapping.setPrefix("/test");
handlerMapping.setApplicationContext(context);
handlerMapping.afterPropertiesSet();
HandlerExecutionChain handler = handlerMapping
.getHandler(new MockHttpServletRequest("GET", "/test/health"));
HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler();
Object handlerMethodBean = handlerMethod.getBean();
assertThat(handlerMethodBean).isInstanceOf(CloudFoundryHealthMvcEndpoint.class);
}
private static class TestEndpoint extends AbstractEndpoint<Object> { private static class TestEndpoint extends AbstractEndpoint<Object> {
TestEndpoint(String id) { TestEndpoint(String id) {
...@@ -124,4 +145,12 @@ public class CloudFoundryEndpointHandlerMappingTests ...@@ -124,4 +145,12 @@ public class CloudFoundryEndpointHandlerMappingTests
} }
private static class TestHealthMvcEndpoint extends HealthMvcEndpoint {
TestHealthMvcEndpoint(HealthEndpoint delegate) {
super(delegate);
}
}
} }
/*
* Copyright 2012-2016 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.cloudfoundry;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link CloudFoundryHealthMvcEndpoint}.
*
* @author Madhura Bhave
*/
public class CloudFoundryHealthMvcEndpointTests {
@Test
public void cloudFoundryHealthEndpointShouldAlwaysReturnAllHealthDetails()
throws Exception {
HealthEndpoint endpoint = mock(HealthEndpoint.class);
given(endpoint.isEnabled()).willReturn(true);
CloudFoundryHealthMvcEndpoint mvc = new CloudFoundryHealthMvcEndpoint(endpoint);
given(endpoint.invoke())
.willReturn(new Health.Builder().up().withDetail("foo", "bar").build());
given(endpoint.isSensitive()).willReturn(false);
Object result = mvc.invoke(null);
assertThat(result instanceof Health).isTrue();
assertThat(((Health) result).getStatus() == Status.UP).isTrue();
assertThat(((Health) result).getDetails().get("foo")).isEqualTo("bar");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment