10 changed files with 807 additions and 7 deletions
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
/* |
||||
* Copyright 2002-2023 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 |
||||
* |
||||
* https://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.security.config.annotation.web.configuration; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.security.authentication.TestAuthentication; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; |
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer; |
||||
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer; |
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator; |
||||
import org.springframework.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
||||
import org.springframework.test.context.web.WebAppConfiguration; |
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* Checks that HandlerMappingIntrospectorRequestTransformer is autowired into |
||||
* {@link org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator}. |
||||
* |
||||
* @author Rob Winch |
||||
*/ |
||||
@ContextConfiguration |
||||
@WebAppConfiguration |
||||
@ExtendWith({ SpringExtension.class }) |
||||
@SecurityTestExecutionListeners |
||||
public class AuthorizationManagerWebInvocationPrivilegeEvaluatorConfigTests { |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Autowired(required = false) |
||||
HttpServletRequestTransformer requestTransformer; |
||||
|
||||
@Autowired |
||||
WebInvocationPrivilegeEvaluator wipe; |
||||
|
||||
@Test |
||||
void mvcEnabledConfigThenHandlerMappingIntrospectorRequestTransformerBeanExists() { |
||||
this.spring.register(MvcEnabledConfig.class).autowire(); |
||||
assertThat(this.requestTransformer).isInstanceOf(HandlerMappingIntrospectorRequestTransformer.class); |
||||
} |
||||
|
||||
@Test |
||||
void mvcNotEnabledThenNoRequestTransformerBeanExists() { |
||||
this.spring.register(MvcNotEnabledConfig.class).autowire(); |
||||
assertThat(this.requestTransformer).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void mvcNotEnabledAndTransformerThenWIPEDelegatesToTransformer() { |
||||
this.spring.register(MvcNotEnabledConfig.class, TransformerConfig.class).autowire(); |
||||
|
||||
this.wipe.isAllowed("/uri", TestAuthentication.authenticatedUser()); |
||||
|
||||
verify(this.requestTransformer).transform(any()); |
||||
} |
||||
|
||||
@Configuration |
||||
static class TransformerConfig { |
||||
|
||||
@Bean |
||||
HttpServletRequestTransformer httpServletRequestTransformer() { |
||||
return mock(HttpServletRequestTransformer.class); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebMvc |
||||
@EnableWebSecurity |
||||
static class MvcEnabledConfig { |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSecurity |
||||
static class MvcNotEnabledConfig { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* Copyright 2002-2023 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 |
||||
* |
||||
* https://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.security.config.annotation.web.configuration; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
import jakarta.servlet.Filter; |
||||
import jakarta.servlet.FilterChain; |
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.ServletRequest; |
||||
import jakarta.servlet.ServletResponse; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; |
||||
import org.springframework.security.test.context.support.WithMockUser; |
||||
import org.springframework.stereotype.Component; |
||||
import org.springframework.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
||||
import org.springframework.test.context.web.WebAppConfiguration; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders; |
||||
import org.springframework.web.context.WebApplicationContext; |
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; |
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector.CachedResult; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
|
||||
/** |
||||
* @author Rob Winch |
||||
*/ |
||||
@ContextConfiguration |
||||
@WebAppConfiguration |
||||
@ExtendWith({ SpringExtension.class }) |
||||
@SecurityTestExecutionListeners |
||||
class HandlerMappingIntrospectorCacheFilterConfigTests { |
||||
|
||||
@Autowired |
||||
WebApplicationContext context; |
||||
|
||||
MockMvc mockMvc; |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Autowired(required = false) |
||||
MvcEnabledConfig.CaptureHandlerMappingIntrospectorCache captureCacheFilter; |
||||
|
||||
@Autowired(required = false) |
||||
HandlerMappingIntrospector hmi; |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void hmiIsCached() throws Exception { |
||||
this.spring.register(MvcEnabledConfig.class).autowire(); |
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) |
||||
.apply(springSecurity()) |
||||
.addFilter(this.captureCacheFilter) |
||||
.build(); |
||||
this.mockMvc.perform(get("/")); |
||||
assertThat(this.captureCacheFilter.cachedResult).isNotNull(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void configurationLoadsIfNoHMI() { |
||||
// no BeanCreationException due to missing HandlerMappingIntrospector
|
||||
this.spring.register(MvcNotEnabledConfig.class).autowire(); |
||||
// ensure assumption of HandlerMappingIntrospector is null is true
|
||||
assertThat(this.hmi).isNull(); |
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebMvc |
||||
@EnableWebSecurity |
||||
static class MvcEnabledConfig { |
||||
|
||||
@Component |
||||
static class CaptureHandlerMappingIntrospectorCache implements Filter { |
||||
|
||||
final HandlerMappingIntrospector hmi; |
||||
|
||||
private CachedResult cachedResult; |
||||
|
||||
CaptureHandlerMappingIntrospectorCache(HandlerMappingIntrospector hmi) { |
||||
this.hmi = hmi; |
||||
} |
||||
|
||||
@Override |
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
||||
throws IOException, ServletException { |
||||
// capture the old cached value to check that caching has already occurred
|
||||
this.cachedResult = this.hmi.setCache((HttpServletRequest) request); |
||||
chain.doFilter(request, response); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@Configuration |
||||
@EnableWebSecurity |
||||
static class MvcNotEnabledConfig { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
/* |
||||
* Copyright 2002-2023 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 |
||||
* |
||||
* https://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.security.web.access; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.Enumeration; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import jakarta.servlet.DispatcherType; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletRequestWrapper; |
||||
|
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; |
||||
|
||||
/** |
||||
* Transforms by passing it into |
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)}. Before, it wraps the |
||||
* {@link HttpServletRequest} to ensure that the methods needed work since some methods by |
||||
* default throw {@link UnsupportedOperationException}. |
||||
* |
||||
* @author Rob Winch |
||||
*/ |
||||
public class HandlerMappingIntrospectorRequestTransformer |
||||
implements AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer { |
||||
|
||||
private final HandlerMappingIntrospector introspector; |
||||
|
||||
public HandlerMappingIntrospectorRequestTransformer(HandlerMappingIntrospector introspector) { |
||||
Assert.notNull(introspector, "introspector canot be null"); |
||||
this.introspector = introspector; |
||||
} |
||||
|
||||
@Override |
||||
public HttpServletRequest transform(HttpServletRequest request) { |
||||
CacheableRequestWrapper cacheableRequest = new CacheableRequestWrapper(request); |
||||
this.introspector.setCache(cacheableRequest); |
||||
return cacheableRequest; |
||||
} |
||||
|
||||
static final class CacheableRequestWrapper extends HttpServletRequestWrapper { |
||||
|
||||
private final Map<String, Object> attributes = new HashMap<>(); |
||||
|
||||
/** |
||||
* Constructs a request object wrapping the given request. |
||||
* @param request the {@link HttpServletRequest} to be wrapped. |
||||
* @throws IllegalArgumentException if the request is null |
||||
*/ |
||||
CacheableRequestWrapper(HttpServletRequest request) { |
||||
super(request); |
||||
} |
||||
|
||||
@Override |
||||
public DispatcherType getDispatcherType() { |
||||
return DispatcherType.REQUEST; |
||||
} |
||||
|
||||
@Override |
||||
public Enumeration<String> getAttributeNames() { |
||||
return Collections.enumeration(this.attributes.keySet()); |
||||
} |
||||
|
||||
@Override |
||||
public Object getAttribute(String name) { |
||||
return this.attributes.get(name); |
||||
} |
||||
|
||||
@Override |
||||
public void setAttribute(String name, Object o) { |
||||
this.attributes.put(name, o); |
||||
} |
||||
|
||||
@Override |
||||
public void removeAttribute(String name) { |
||||
this.attributes.remove(name); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,206 @@
@@ -0,0 +1,206 @@
|
||||
/* |
||||
* Copyright 2002-2023 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 |
||||
* |
||||
* https://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.security.web.access; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import jakarta.servlet.DispatcherType; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import org.assertj.core.api.AssertionsForClassTypes; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.mockito.ArgumentCaptor; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
/** |
||||
* @author Rob Winch |
||||
*/ |
||||
@ExtendWith(MockitoExtension.class) |
||||
class HandlerMappingIntrospectorRequestTransformerTests { |
||||
|
||||
@Mock |
||||
HandlerMappingIntrospector hmi; |
||||
|
||||
HandlerMappingIntrospectorRequestTransformer transformer; |
||||
|
||||
@BeforeEach |
||||
void setup() { |
||||
this.transformer = new HandlerMappingIntrospectorRequestTransformer(this.hmi); |
||||
} |
||||
|
||||
@Test |
||||
void constructorWhenHmiIsNullThenIllegalArgumentException() { |
||||
AssertionsForClassTypes.assertThatExceptionOfType(IllegalArgumentException.class) |
||||
.isThrownBy(() -> new HandlerMappingIntrospectorRequestTransformer(null)); |
||||
} |
||||
|
||||
@Test |
||||
void transformThenNewRequestPassedToSetCache() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
|
||||
HttpServletRequest transformedRequest = this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
assertThat(transformedRequest).isNotEqualTo(request); |
||||
} |
||||
|
||||
@Test |
||||
void transformThenResultPassedToSetCache() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
|
||||
HttpServletRequest transformedRequest = this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
assertThat(requestArg.getValue()).isEqualTo(transformedRequest); |
||||
} |
||||
|
||||
/** |
||||
* The request passed into the transformer does not allow interactions on certain |
||||
* methods, we need to ensure that the methods used by |
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden. |
||||
*/ |
||||
@Test |
||||
void transformThenResultDoesNotDelegateToSetAttribute() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
String attrName = "any"; |
||||
String attrValue = "value"; |
||||
transformedRequest.setAttribute(attrName, attrValue); |
||||
verifyNoInteractions(request); |
||||
assertThat(transformedRequest.getAttribute(attrName)).isEqualTo(attrValue); |
||||
} |
||||
|
||||
@Test |
||||
void transformThenSetAttributeWorks() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
String attrName = "any"; |
||||
String attrValue = "value"; |
||||
transformedRequest.setAttribute(attrName, attrValue); |
||||
assertThat(transformedRequest.getAttribute(attrName)).isEqualTo(attrValue); |
||||
} |
||||
|
||||
/** |
||||
* The request passed into the transformer does not allow interactions on certain |
||||
* methods, we need to ensure that the methods used by |
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden. |
||||
*/ |
||||
@Test |
||||
void transformThenResultDoesNotDelegateToGetAttribute() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
transformedRequest.getAttribute("any"); |
||||
verifyNoInteractions(request); |
||||
} |
||||
|
||||
/** |
||||
* The request passed into the transformer does not allow interactions on certain |
||||
* methods, we need to ensure that the methods used by |
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden. |
||||
*/ |
||||
@Test |
||||
void transformThenResultDoesNotDelegateToGetAttributeNames() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
transformedRequest.getAttributeNames(); |
||||
verifyNoInteractions(request); |
||||
} |
||||
|
||||
@Test |
||||
void transformThenGetAttributeNamesWorks() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
String attrName = "any"; |
||||
String attrValue = "value"; |
||||
transformedRequest.setAttribute(attrName, attrValue); |
||||
assertThat(Collections.list(transformedRequest.getAttributeNames())).containsExactly(attrName); |
||||
} |
||||
|
||||
/** |
||||
* The request passed into the transformer does not allow interactions on certain |
||||
* methods, we need to ensure that the methods used by |
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden. |
||||
*/ |
||||
@Test |
||||
void transformThenResultDoesNotDelegateToRemoveAttribute() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
transformedRequest.removeAttribute("any"); |
||||
verifyNoInteractions(request); |
||||
} |
||||
|
||||
/** |
||||
* The request passed into the transformer does not allow interactions on certain |
||||
* methods, we need to ensure that the methods used by |
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden. |
||||
*/ |
||||
@Test |
||||
void transformThenResultDoesNotDelegateToGetDispatcherType() { |
||||
HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
this.transformer.transform(request); |
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class); |
||||
verify(this.hmi).setCache(requestArg.capture()); |
||||
HttpServletRequest transformedRequest = requestArg.getValue(); |
||||
assertThat(transformedRequest.getDispatcherType()).isEqualTo(DispatcherType.REQUEST); |
||||
verifyNoInteractions(request); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue