diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java index 38d0e47433..0ac971ff0c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityInterceptor.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsUtils; @@ -57,7 +58,7 @@ class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, - Object o) throws Exception { + Object handler) throws Exception { if (CorsUtils.isPreFlightRequest(request)) { return true; } @@ -70,7 +71,11 @@ class CloudFoundrySecurityInterceptor extends HandlerInterceptorAdapter { throw new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available"); } - HandlerMethod handlerMethod = (HandlerMethod) o; + HandlerMethod handlerMethod = (HandlerMethod) handler; + if (HttpMethod.OPTIONS.matches(request.getMethod()) + && !(handlerMethod.getBean() instanceof MvcEndpoint)) { + return true; + } MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean(); check(request, mvcEndpoint); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java index e9c6d3b785..b043def19e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptor.java @@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsUtils; @@ -60,6 +61,10 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; + if (HttpMethod.OPTIONS.matches(request.getMethod()) + && !(handlerMethod.getBean() instanceof MvcEndpoint)) { + return true; + } MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean(); if (!mvcEndpoint.isSensitive()) { return true; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java new file mode 100644 index 0000000000..2d19f2b505 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointSecureOptionsTests.java @@ -0,0 +1,146 @@ +/* + * 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.endpoint.mvc; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link HeapdumpMvcEndpoint} OPTIONS call with security. + * + * @author Phillip Webb + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class HeapdumpMvcEndpointSecureOptionsTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Autowired + private TestHeapdumpMvcEndpoint endpoint; + + @Before + public void setup() { + this.context.getBean(HeapdumpMvcEndpoint.class).setEnabled(true); + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @After + public void reset() { + this.endpoint.reset(); + } + + @Test + public void invokeOptionsShouldReturnSize() throws Exception { + this.mvc.perform(options("/heapdump")).andExpect(status().isOk()); + } + + @Import({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class }) + @Configuration + public static class TestConfiguration { + + @Bean + public HeapdumpMvcEndpoint endpoint() { + return new TestHeapdumpMvcEndpoint(); + } + + } + + private static class TestHeapdumpMvcEndpoint extends HeapdumpMvcEndpoint { + + private boolean available; + + private boolean locked; + + private String heapDump; + + TestHeapdumpMvcEndpoint() { + super(TimeUnit.SECONDS.toMillis(1)); + reset(); + } + + public void reset() { + this.available = true; + this.locked = false; + this.heapDump = "HEAPDUMP"; + } + + @Override + protected HeapDumper createHeapDumper() { + return new HeapDumper() { + + @Override + public void dumpHeap(File file, boolean live) + throws IOException, InterruptedException { + if (!TestHeapdumpMvcEndpoint.this.available) { + throw new HeapDumperUnavailableException("Not available", null); + } + if (TestHeapdumpMvcEndpoint.this.locked) { + throw new InterruptedException(); + } + if (file.exists()) { + throw new IOException("File exists"); + } + FileCopyUtils.copy(TestHeapdumpMvcEndpoint.this.heapDump.getBytes(), + file); + } + + }; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java index 03df244c4c..af4c363b07 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HeapdumpMvcEndpointTests.java @@ -47,6 +47,7 @@ import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -108,6 +109,11 @@ public class HeapdumpMvcEndpointTests { assertThat(uncompressed).isEqualTo("HEAPDUMP".getBytes()); } + @Test + public void invokeOptionsShouldReturnSize() throws Exception { + this.mvc.perform(options("/heapdump")).andExpect(status().isOk()); + } + @Import({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,