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 b043def19e..d60b271912 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 @@ -27,6 +27,10 @@ import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.web.cors.CorsUtils; import org.springframework.web.method.HandlerMethod; @@ -69,15 +73,35 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { if (!mvcEndpoint.isSensitive()) { return true; } + if (isUserAllowedAccess(request)) { + return true; + } + sendFailureResponse(request, response); + return false; + } + + private boolean isUserAllowedAccess(HttpServletRequest request) { + AuthoritiesValidator authoritiesValidator = null; + if (isSpringSecurityAvailable()) { + authoritiesValidator = new AuthoritiesValidator(); + } for (String role : this.roles) { if (request.isUserInRole(role)) { return true; } + if (authoritiesValidator != null && authoritiesValidator.hasAuthority(role)) { + return true; + } } - sendFailureResponse(request, response); return false; } + private boolean isSpringSecurityAvailable() { + return ClassUtils.isPresent( + "org.springframework.security.config.annotation.web.WebSecurityConfigurer", + getClass().getClassLoader()); + } + private void sendFailureResponse(HttpServletRequest request, HttpServletResponse response) throws Exception { if (request.getUserPrincipal() != null) { @@ -101,4 +125,19 @@ public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter { } } + private class AuthoritiesValidator { + + private boolean hasAuthority(String role) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + for (GrantedAuthority authority : authentication.getAuthorities()) { + if (authority.getAuthority().equals(role)) { + return true; + } + } + } + return false; + } + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java index cbfa698133..1aaedad86a 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpointSecurityInterceptorTests.java @@ -18,7 +18,9 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.security.Principal; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Set; import javax.servlet.http.HttpServletResponse; @@ -31,9 +33,13 @@ import org.springframework.boot.test.rule.OutputCapture; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.method.HandlerMethod; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -123,6 +129,30 @@ public class MvcEndpointSecurityInterceptorTests { "Access is denied. User must have one of the these roles: SUPER_HERO"); } + @Test + public void sensitiveEndpointIfRoleNotCorrectShouldCheckAuthorities() throws Exception { + Principal principal = mock(Principal.class); + this.request.setUserPrincipal(principal); + Authentication authentication = mock(Authentication.class); + Set authorities = Collections.singleton(new SimpleGrantedAuthority("SUPER_HERO")); + doReturn(authorities).when(authentication).getAuthorities(); + SecurityContextHolder.getContext().setAuthentication(authentication); + assertThat(this.securityInterceptor.preHandle(this.request, this.response, + this.handlerMethod)).isTrue(); + } + + @Test + public void sensitiveEndpointIfRoleAndAuthoritiesNotCorrectShouldNotAllowAccess() throws Exception { + Principal principal = mock(Principal.class); + this.request.setUserPrincipal(principal); + Authentication authentication = mock(Authentication.class); + Set authorities = Collections.singleton(new SimpleGrantedAuthority("HERO")); + doReturn(authorities).when(authentication).getAuthorities(); + SecurityContextHolder.getContext().setAuthentication(authentication); + assertThat(this.securityInterceptor.preHandle(this.request, this.response, + this.handlerMethod)).isFalse(); + } + private static class TestEndpoint extends AbstractEndpoint { TestEndpoint(String id) { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java new file mode 100644 index 0000000000..6d5ad0ceef --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/NoSpringSecurityMvcEndpointSecurityInterceptorTests.java @@ -0,0 +1,109 @@ +/* + * 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.security.Principal; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.endpoint.AbstractEndpoint; +import org.springframework.boot.junit.runner.classpath.ClassPathExclusions; +import org.springframework.boot.junit.runner.classpath.ModifiedClassPathRunner; +import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.method.HandlerMethod; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * @author Madhura Bhave + */ +@RunWith(ModifiedClassPathRunner.class) +@ClassPathExclusions("spring-security-*.jar") +public class NoSpringSecurityMvcEndpointSecurityInterceptorTests { + + @Rule + public OutputCapture output = new OutputCapture(); + + private MvcEndpointSecurityInterceptor securityInterceptor; + + private TestMvcEndpoint mvcEndpoint; + + private TestEndpoint endpoint; + + private HandlerMethod handlerMethod; + + private MockHttpServletRequest request; + + private HttpServletResponse response; + + private MockServletContext servletContext; + + private List roles; + + @Before + public void setup() throws Exception { + this.roles = Arrays.asList("SUPER_HERO"); + this.securityInterceptor = new MvcEndpointSecurityInterceptor(true, this.roles); + this.endpoint = new TestEndpoint("a"); + this.mvcEndpoint = new TestMvcEndpoint(this.endpoint); + this.handlerMethod = new HandlerMethod(this.mvcEndpoint, "invoke"); + this.servletContext = new MockServletContext(); + this.request = new MockHttpServletRequest(this.servletContext); + this.response = mock(HttpServletResponse.class); + } + + @Test + public void sensitiveEndpointIfRoleNotPresentShouldNotValidateAuthorities() throws Exception { + Principal principal = mock(Principal.class); + this.request.setUserPrincipal(principal); + this.servletContext.declareRoles("HERO"); + assertThat(this.securityInterceptor.preHandle(this.request, this.response, + this.handlerMethod)).isFalse(); + } + + private static class TestEndpoint extends AbstractEndpoint { + + TestEndpoint(String id) { + super(id); + } + + @Override + public Object invoke() { + return null; + } + + } + + private static class TestMvcEndpoint extends EndpointMvcAdapter { + + TestMvcEndpoint(TestEndpoint delegate) { + super(delegate); + } + + } +} +