1
0
mirror of synced 2026-09-12 12:15:13 +00:00

Move Web Access API

Issue gh-17847
This commit is contained in:
Josh Cummings
2025-09-02 16:06:03 -06:00
parent 3182883e2e
commit fa4806dbcc
35 changed files with 67 additions and 54 deletions
@@ -0,0 +1,125 @@
/*
* Copyright 2004-present 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 org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* Tests
* {@link org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator}.
*
* @author Ben Alex
*/
public class DefaultWebInvocationPrivilegeEvaluatorTests {
private AccessDecisionManager adm;
private FilterInvocationSecurityMetadataSource ods;
private RunAsManager ram;
private FilterSecurityInterceptor interceptor;
@BeforeEach
public final void setUp() {
this.interceptor = new FilterSecurityInterceptor();
this.ods = Mockito.mock(FilterInvocationSecurityMetadataSource.class);
this.adm = Mockito.mock(AccessDecisionManager.class);
this.ram = Mockito.mock(RunAsManager.class);
this.interceptor.setAuthenticationManager(Mockito.mock(AuthenticationManager.class));
this.interceptor.setSecurityMetadataSource(this.ods);
this.interceptor.setAccessDecisionManager(this.adm);
this.interceptor.setRunAsManager(this.ram);
this.interceptor.setApplicationEventPublisher(Mockito.mock(ApplicationEventPublisher.class));
SecurityContextHolder.clearContext();
}
@Test
public void permitsAccessIfNoMatchingAttributesAndPublicInvocationsAllowed() {
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null);
Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class)))
.isTrue();
}
@Test
public void deniesAccessIfNoMatchingAttributesAndPublicInvocationsNotAllowed() {
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null);
this.interceptor.setRejectPublicInvocations(true);
Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class)))
.isFalse();
}
@Test
public void deniesAccessIfAuthenticationIsNull() {
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", null)).isFalse();
}
@Test
public void allowsAccessIfAccessDecisionManagerDoes() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isTrue();
}
@SuppressWarnings("unchecked")
@Test
public void deniesAccessIfAccessDecisionManagerDoes() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
BDDMockito.willThrow(new AccessDeniedException(""))
.given(this.adm)
.decide(ArgumentMatchers.any(Authentication.class), ArgumentMatchers.any(), ArgumentMatchers.anyList());
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isFalse();
}
@Test
public void isAllowedWhenServletContextIsSetThenPassedFilterInvocationHasServletContext() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
MockServletContext servletContext = new MockServletContext();
ArgumentCaptor<FilterInvocation> filterInvocationArgumentCaptor = ArgumentCaptor
.forClass(FilterInvocation.class);
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
wipe.setServletContext(servletContext);
wipe.isAllowed("/foo/index.jsp", token);
Mockito.verify(this.adm)
.decide(ArgumentMatchers.eq(token), filterInvocationArgumentCaptor.capture(), ArgumentMatchers.any());
Assertions.assertThat(filterInvocationArgumentCaptor.getValue().getRequest().getServletContext()).isNotNull();
}
}
@@ -0,0 +1,192 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.channel;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import jakarta.servlet.FilterChain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
/**
* Tests {@link ChannelDecisionManagerImpl}.
*
* @author Ben Alex
*/
@SuppressWarnings("unchecked")
public class ChannelDecisionManagerImplTests {
@Test
public void testCannotSetEmptyChannelProcessorsList() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThatIllegalArgumentException().isThrownBy(() -> {
cdm.setChannelProcessors(new Vector());
cdm.afterPropertiesSet();
}).withMessage("A list of ChannelProcessors is required");
}
@Test
public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
List list = new Vector();
list.add("THIS IS NOT A CHANNELPROCESSOR");
assertThatIllegalArgumentException().isThrownBy(() -> cdm.setChannelProcessors(list));
}
@Test
public void testCannotSetNullChannelProcessorsList() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThatIllegalArgumentException().isThrownBy(() -> {
cdm.setChannelProcessors(null);
cdm.afterPropertiesSet();
}).withMessage("A list of ChannelProcessors is required");
}
@Test
public void testDecideIsOperational() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
List<ConfigAttribute> cad = SecurityConfig.createList("xyz");
cdm.decide(fi, cad);
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
}
@Test
public void testAnyChannelAttributeCausesProcessorsToBeSkipped() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
List list = new Vector();
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
cdm.decide(fi, SecurityConfig.createList(new String[] { "abc", "ANY_CHANNEL" }));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
cdm.decide(fi, SecurityConfig.createList("SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT"));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDelegatesSupports() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
assertThat(cdm.supports(new SecurityConfig("xyz"))).isTrue();
assertThat(cdm.supports(new SecurityConfig("abc"))).isTrue();
assertThat(cdm.supports(new SecurityConfig("UNSUPPORTED"))).isFalse();
}
@Test
public void testGettersSetters() {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThat(cdm.getChannelProcessors()).isNull();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
assertThat(cdm.getChannelProcessors()).isEqualTo(list);
}
@Test
public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThatIllegalArgumentException().isThrownBy(cdm::afterPropertiesSet)
.withMessage("A list of ChannelProcessors is required");
}
private class MockChannelProcessor implements ChannelProcessor {
private String configAttribute;
private boolean failIfCalled;
MockChannelProcessor(String configAttribute, boolean failIfCalled) {
this.configAttribute = configAttribute;
this.failIfCalled = failIfCalled;
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException {
Iterator iter = config.iterator();
if (this.failIfCalled) {
fail("Should not have called this channel processor: " + this.configAttribute);
}
while (iter.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) iter.next();
if (attr.getAttribute().equals(this.configAttribute)) {
invocation.getHttpResponse().sendRedirect("/redirected");
return;
}
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute().equals(this.configAttribute);
}
}
}
@@ -0,0 +1,191 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.channel;
import java.io.IOException;
import java.util.Collection;
import jakarta.servlet.FilterChain;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link ChannelProcessingFilter}.
*
* @author Ben Alex
*/
public class ChannelProcessingFilterTests {
@Test
public void testDetectsMissingChannelDecisionManager() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "MOCK");
filter.setSecurityMetadataSource(fids);
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
}
@Test
public void testDetectsMissingFilterInvocationSecurityMetadataSource() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK"));
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
}
@Test
public void testDetectsSupportedConfigAttribute() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true,
"SUPPORTS_MOCK_ONLY");
filter.setSecurityMetadataSource(fids);
filter.afterPropertiesSet();
}
@Test
public void testDetectsUnsupportedConfigAttribute() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true,
"SUPPORTS_MOCK_ONLY", "INVALID_ATTRIBUTE");
filter.setSecurityMetadataSource(fids);
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
}
@Test
public void testDoFilterWhenManagerDoesCommitResponse() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(true, "SOME_ATTRIBUTE"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE");
filter.setSecurityMetadataSource(fids);
MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build();
request.setQueryString("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, mock(FilterChain.class));
}
@Test
public void testDoFilterWhenManagerDoesNotCommitResponse() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SOME_ATTRIBUTE"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE");
filter.setSecurityMetadataSource(fids);
MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build();
request.setQueryString("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, mock(FilterChain.class));
}
@Test
public void testDoFilterWhenNullConfigAttributeReturned() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "NOT_USED"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "NOT_USED");
filter.setSecurityMetadataSource(fids);
MockHttpServletRequest request = TestMockHttpServletRequests.get("/PATH_NOT_MATCHING_CONFIG_ATTRIBUTE").build();
request.setQueryString("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, mock(FilterChain.class));
}
@Test
public void testGetterSetters() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK"));
assertThat(filter.getChannelDecisionManager() != null).isTrue();
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", false, "MOCK");
filter.setSecurityMetadataSource(fids);
assertThat(filter.getSecurityMetadataSource()).isSameAs(fids);
filter.afterPropertiesSet();
}
private class MockChannelDecisionManager implements ChannelDecisionManager {
private String supportAttribute;
private boolean commitAResponse;
MockChannelDecisionManager(boolean commitAResponse, String supportAttribute) {
this.commitAResponse = commitAResponse;
this.supportAttribute = supportAttribute;
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException {
if (this.commitAResponse) {
invocation.getHttpResponse().sendRedirect("/redirected");
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute().equals(this.supportAttribute);
}
}
private class MockFilterInvocationDefinitionMap implements FilterInvocationSecurityMetadataSource {
private Collection<ConfigAttribute> toReturn;
private String servletPath;
private boolean provideIterator;
MockFilterInvocationDefinitionMap(String servletPath, boolean provideIterator, String... toReturn) {
this.servletPath = servletPath;
this.toReturn = SecurityConfig.createList(toReturn);
this.provideIterator = provideIterator;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
if (this.servletPath.equals(fi.getHttpRequest().getServletPath())) {
return this.toReturn;
}
else {
return null;
}
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
if (!this.provideIterator) {
return null;
}
return this.toReturn;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.channel;
import jakarta.servlet.FilterChain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link InsecureChannelProcessor}.
*
* @author Ben Alex
*/
public class InsecureChannelProcessorTests {
@Test
public void testDecideDetectsAcceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL"));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDecideDetectsUnacceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.decide(fi,
SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL" }));
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
}
@Test
public void testDecideRejectsNulls() throws Exception {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.afterPropertiesSet();
assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null));
}
@Test
public void testGettersSetters() {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
assertThat(processor.getInsecureKeyword()).isEqualTo("REQUIRES_INSECURE_CHANNEL");
processor.setInsecureKeyword("X");
assertThat(processor.getInsecureKeyword()).isEqualTo("X");
assertThat(processor.getEntryPoint() != null).isTrue();
processor.setEntryPoint(null);
assertThat(processor.getEntryPoint() == null).isTrue();
}
@Test
public void testMissingEntryPoint() throws Exception {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.setEntryPoint(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("entryPoint required");
}
@Test
public void testMissingSecureChannelKeyword() throws Exception {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.setInsecureKeyword(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("insecureKeyword required");
processor.setInsecureKeyword("");
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("insecureKeyword required");
}
@Test
public void testSupports() {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
assertThat(processor.supports(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"))).isTrue();
assertThat(processor.supports(null)).isFalse();
assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse();
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.channel;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.RedirectStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link RetryWithHttpEntryPoint}.
*
* @author Ben Alex
*/
public class RetryWithHttpEntryPointTests {
@Test
public void testDetectsMissingPortMapper() {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null));
}
@Test
public void testGettersSetters() {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
PortMapper portMapper = mock(PortMapper.class);
RedirectStrategy redirector = mock(RedirectStrategy.class);
ep.setPortMapper(portMapper);
ep.setRedirectStrategy(redirector);
assertThat(ep.getPortMapper()).isSameAs(portMapper);
assertThat(ep.getRedirectStrategy()).isSameAs(redirector);
}
@Test
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(443);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello/pathInfo.html?open=true");
}
@Test
public void testNormalOperationWithNullQueryString() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(443);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello");
}
@Test
public void testOperationWhenTargetPortIsUnknown() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp");
request.setQueryString("open=true");
request.setScheme("https");
request.setServerName("www.example.com");
request.setServerPort(8768);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true");
}
@Test
public void testOperationWithNonStandardPort() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(9999);
MockHttpServletResponse response = new MockHttpServletResponse();
PortMapperImpl portMapper = new PortMapperImpl();
Map<String, String> map = new HashMap<>();
map.put("8888", "9999");
portMapper.setPortMappings(map);
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(portMapper);
ep.commence(request, response);
assertThat(response.getRedirectedUrl())
.isEqualTo("http://localhost:8888/bigWebApp/hello/pathInfo.html?open=true");
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.channel;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.PortMapperImpl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link RetryWithHttpsEntryPoint}.
*
* @author Ben Alex
*/
public class RetryWithHttpsEntryPointTests {
@Test
public void testDetectsMissingPortMapper() {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null));
}
@Test
public void testGettersSetters() {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
assertThat(ep.getPortMapper() != null).isTrue();
}
@Test
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(80);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl())
.isEqualTo("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true");
}
@Test
public void testNormalOperationWithNullQueryString() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(80);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello");
}
@Test
public void testOperationWhenTargetPortIsUnknown() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp");
request.setQueryString("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(8768);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true");
}
@Test
public void testOperationWithNonStandardPort() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(8888);
MockHttpServletResponse response = new MockHttpServletResponse();
PortMapperImpl portMapper = new PortMapperImpl();
Map<String, String> map = new HashMap<>();
map.put("8888", "9999");
portMapper.setPortMappings(map);
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(portMapper);
ep.commence(request, response);
assertThat(response.getRedirectedUrl())
.isEqualTo("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true");
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.channel;
import jakarta.servlet.FilterChain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link SecureChannelProcessor}.
*
* @author Ben Alex
*/
public class SecureChannelProcessorTests {
@Test
public void testDecideDetectsAcceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL"));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDecideDetectsUnacceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.decide(fi,
SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL" }));
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
}
@Test
public void testDecideRejectsNulls() throws Exception {
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.afterPropertiesSet();
assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null));
}
@Test
public void testGettersSetters() {
SecureChannelProcessor processor = new SecureChannelProcessor();
assertThat(processor.getSecureKeyword()).isEqualTo("REQUIRES_SECURE_CHANNEL");
processor.setSecureKeyword("X");
assertThat(processor.getSecureKeyword()).isEqualTo("X");
assertThat(processor.getEntryPoint() != null).isTrue();
processor.setEntryPoint(null);
assertThat(processor.getEntryPoint() == null).isTrue();
}
@Test
public void testMissingEntryPoint() throws Exception {
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.setEntryPoint(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("entryPoint required");
}
@Test
public void testMissingSecureChannelKeyword() throws Exception {
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.setSecureKeyword(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("secureKeyword required");
processor.setSecureKeyword("");
assertThatIllegalArgumentException().isThrownBy(() -> processor.afterPropertiesSet())
.withMessage("secureKeyword required");
}
@Test
public void testSupports() {
SecureChannelProcessor processor = new SecureChannelProcessor();
assertThat(processor.supports(new SecurityConfig("REQUIRES_SECURE_CHANNEL"))).isTrue();
assertThat(processor.supports(null)).isFalse();
assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse();
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2004-present 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.expression;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class DefaultWebSecurityExpressionHandlerTests {
@Mock
private AuthenticationTrustResolver trustResolver;
@Mock
private Authentication authentication;
@Mock
private FilterInvocation invocation;
private DefaultWebSecurityExpressionHandler handler;
@BeforeEach
public void setup() {
this.handler = new DefaultWebSecurityExpressionHandler();
}
@AfterEach
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test
public void expressionPropertiesAreResolvedAgainstAppContextBeans() {
StaticApplicationContext appContext = new StaticApplicationContext();
RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A");
appContext.registerBeanDefinition("role", bean);
this.handler.setApplicationContext(appContext);
EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class),
mock(FilterInvocation.class));
ExpressionParser parser = this.handler.getExpressionParser();
assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
}
@Test
public void setTrustResolverNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null));
}
@Test
public void createEvaluationContextCustomTrustResolver() {
this.handler.setTrustResolver(this.trustResolver);
Expression expression = this.handler.getExpressionParser().parseExpression("anonymous");
EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.invocation);
assertThat(expression.getValue(context, Boolean.class)).isFalse();
verify(this.trustResolver).isAnonymous(this.authentication);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2004-present 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.expression;
import java.util.Collection;
import java.util.LinkedHashMap;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* @author Luke Taylor
*/
public class ExpressionBasedFilterInvocationSecurityMetadataSourceTests {
@Test
public void expectedAttributeIsReturned() {
final String expression = "hasRole('X')";
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList(expression));
ExpressionBasedFilterInvocationSecurityMetadataSource mds = new ExpressionBasedFilterInvocationSecurityMetadataSource(
requestMap, new DefaultWebSecurityExpressionHandler());
assertThat(mds.getAllConfigAttributes()).hasSize(1);
Collection<ConfigAttribute> attrs = mds.getAttributes(new FilterInvocation("/path", "GET"));
assertThat(attrs).hasSize(1);
WebExpressionConfigAttribute attribute = (WebExpressionConfigAttribute) attrs.toArray()[0];
assertThat(attribute.getAttribute()).isNull();
assertThat(attribute.getAuthorizeExpression().getExpressionString()).isEqualTo(expression);
assertThat(attribute.toString()).isEqualTo(expression);
}
@Test
public void invalidExpressionIsRejected() {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList("hasRole('X'"));
assertThatIllegalArgumentException()
.isThrownBy(() -> new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
new DefaultWebSecurityExpressionHandler()));
}
}
@@ -0,0 +1,116 @@
/*
* Copyright 2004-present 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.expression;
import java.util.ArrayList;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* @author Luke Taylor
*/
@SuppressWarnings({ "unchecked" })
public class WebExpressionVoterTests {
private Authentication user = new TestingAuthenticationToken("user", "pass", "X");
@Test
public void supportsWebConfigAttributeAndFilterInvocation() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(
new WebExpressionConfigAttribute(mock(Expression.class), mock(EvaluationContextPostProcessor.class))))
.isTrue();
assertThat(voter.supports(FilterInvocation.class)).isTrue();
assertThat(voter.supports(MethodInvocation.class)).isFalse();
}
@Test
public void abstainsIfNoAttributeFound() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(
voter.vote(this.user, new FilterInvocation("/path", "GET"), SecurityConfig.createList("A", "B", "C")))
.isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN);
}
@Test
public void grantsAccessIfExpressionIsTrueDeniesIfFalse() {
WebExpressionVoter voter = new WebExpressionVoter();
Expression ex = mock(Expression.class);
EvaluationContextPostProcessor postProcessor = mock(EvaluationContextPostProcessor.class);
given(postProcessor.postProcess(any(EvaluationContext.class), any(FilterInvocation.class)))
.willAnswer((invocation) -> invocation.getArgument(0));
WebExpressionConfigAttribute weca = new WebExpressionConfigAttribute(ex, postProcessor);
EvaluationContext ctx = mock(EvaluationContext.class);
SecurityExpressionHandler eh = mock(SecurityExpressionHandler.class);
FilterInvocation fi = new FilterInvocation("/path", "GET");
voter.setExpressionHandler(eh);
given(eh.createEvaluationContext(this.user, fi)).willReturn(ctx);
given(ex.getValue(ctx, Boolean.class)).willReturn(Boolean.TRUE, Boolean.FALSE);
ArrayList attributes = new ArrayList();
attributes.addAll(SecurityConfig.createList("A", "B", "C"));
attributes.add(weca);
assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_GRANTED);
// Second time false
assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED);
}
// SEC-2507
@Test
public void supportFilterInvocationSubClass() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(FilterInvocationChild.class)).isTrue();
}
@Test
public void supportFilterInvocation() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(FilterInvocation.class)).isTrue();
}
@Test
public void supportsObjectIsFalse() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(Object.class)).isFalse();
}
private static class FilterInvocationChild extends FilterInvocation {
FilterInvocationChild(ServletRequest request, ServletResponse response, FilterChain chain) {
super(request, response, chain);
}
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.intercept;
import java.util.Collection;
import java.util.LinkedHashMap;
import jakarta.servlet.FilterChain;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests {@link DefaultFilterInvocationSecurityMetadataSource}.
*
* @author Ben Alex
*/
public class DefaultFilterInvocationSecurityMetadataSourceTests {
private DefaultFilterInvocationSecurityMetadataSource fids;
private Collection<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
private void createFids(String pattern, HttpMethod method) {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
requestMap.put(PathPatternRequestMatcher.pathPattern(method, pattern), this.def);
this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
}
@Test
public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
createFids("/secure/super/**", null);
FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null, null, "GET");
assertThat(this.fids.getAttributes(fi)).isEqualTo(this.def);
}
/**
* SEC-501. Note that as of 2.0, lower case comparisons are the default for this
* class.
*/
@Test
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
createFids("/secure/super/**", null);
FilterInvocation fi = createFilterInvocation("/secure", "/super/somefile.html", null, "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
}
@Test
public void lookupRequiringExactMatchIsSuccessful() {
createFids("/SeCurE/super/**", null);
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null, null, "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
}
@Test
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
createFids("/someAdminPage.html**", null);
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, "a=/test", "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response); // see SEC-161 (it should truncate after ?
// sign).isEqualTo(def)
}
@Test
public void httpMethodLookupSucceeds() {
createFids("/somepage**", HttpMethod.GET);
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEqualTo(this.def);
}
@Test
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
createFids("/somepage**", null);
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEqualTo(this.def);
}
@Test
public void requestWithDifferentHttpMethodDoesntMatch() {
createFids("/somepage**", HttpMethod.GET);
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEmpty();
}
// SEC-1236
@Test
public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
Collection<ConfigAttribute> userAttrs = SecurityConfig.createList("A");
requestMap.put(PathPatternRequestMatcher.pathPattern("/user/**"), userAttrs);
requestMap.put(PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/teller/**"),
SecurityConfig.createList("B"));
this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
FilterInvocation fi = createFilterInvocation("/user", null, null, "GET");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEqualTo(userAttrs);
}
/**
* Check fixes for SEC-321
*/
@Test
public void extraQuestionMarkStillMatches() {
createFids("/someAdminPage.html*", null);
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, null, "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
fi = createFilterInvocation("/someAdminPage.html", null, "?", "GET");
response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
}
private FilterInvocation createFilterInvocation(String servletPath, String pathInfo, String queryString,
String method) {
MockHttpServletRequest request = TestMockHttpServletRequests.request(method)
.requestUri(null, servletPath, pathInfo)
.queryString(queryString)
.build();
return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class));
}
}
@@ -0,0 +1,198 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.intercept;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.event.AuthorizedEvent;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests {@link FilterSecurityInterceptor}.
*
* @author Ben Alex
* @author Luke Taylor
* @author Rob Winch
*/
public class FilterSecurityInterceptorTests {
private AuthenticationManager am;
private AccessDecisionManager adm;
private FilterInvocationSecurityMetadataSource ods;
private RunAsManager ram;
private FilterSecurityInterceptor interceptor;
private ApplicationEventPublisher publisher;
@BeforeEach
public final void setUp() {
this.interceptor = new FilterSecurityInterceptor();
this.am = mock(AuthenticationManager.class);
this.ods = mock(FilterInvocationSecurityMetadataSource.class);
this.adm = mock(AccessDecisionManager.class);
this.ram = mock(RunAsManager.class);
this.publisher = mock(ApplicationEventPublisher.class);
this.interceptor.setAuthenticationManager(this.am);
this.interceptor.setSecurityMetadataSource(this.ods);
this.interceptor.setAccessDecisionManager(this.adm);
this.interceptor.setRunAsManager(this.ram);
this.interceptor.setApplicationEventPublisher(this.publisher);
SecurityContextHolder.clearContext();
}
@AfterEach
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass() throws Exception {
given(this.adm.supports(FilterInvocation.class)).willReturn(true);
assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet);
}
@Test
public void testEnsuresRunAsManagerSupportsFilterInvocationClass() throws Exception {
given(this.adm.supports(FilterInvocation.class)).willReturn(false);
assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet);
}
/**
* We just test invocation works in a success event. There is no need to test access
* denied events as the abstract parent enforces that logic, which is extensively
* tested separately.
*/
@Test
public void testSuccessfulInvocation() throws Throwable {
// Setup a Context
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
SecurityContextHolder.getContext().setAuthentication(token);
FilterInvocation fi = createinvocation();
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
this.interceptor.invoke(fi);
// SEC-1697
verify(this.publisher, never()).publishEvent(any(AuthorizedEvent.class));
}
@Test
public void afterInvocationIsNotInvokedIfExceptionThrown() throws Exception {
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
SecurityContextHolder.getContext().setAuthentication(token);
FilterInvocation fi = createinvocation();
FilterChain chain = fi.getChain();
willThrow(new RuntimeException()).given(chain)
.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
AfterInvocationManager aim = mock(AfterInvocationManager.class);
this.interceptor.setAfterInvocationManager(aim);
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi));
verifyNoMoreInteractions(aim);
}
// SEC-1967
@Test
@SuppressWarnings("unchecked")
public void finallyInvocationIsInvokedIfExceptionThrown() throws Exception {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
token.setAuthenticated(true);
ctx.setAuthentication(token);
RunAsManager runAsManager = mock(RunAsManager.class);
given(runAsManager.buildRunAs(eq(token), any(), anyCollection()))
.willReturn(new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), token.getClass()));
this.interceptor.setRunAsManager(runAsManager);
FilterInvocation fi = createinvocation();
FilterChain chain = fi.getChain();
willThrow(new RuntimeException()).given(chain)
.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
AfterInvocationManager aim = mock(AfterInvocationManager.class);
this.interceptor.setAfterInvocationManager(aim);
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi));
// Check we've changed back
assertThat(SecurityContextHolder.getContext()).isSameAs(ctx);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token);
}
@Test
// gh-4997
public void doFilterWhenObserveOncePerRequestThenAttributeNotSet() throws Exception {
this.interceptor.setObserveOncePerRequest(false);
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest();
this.interceptor.doFilter(request, response, new MockFilterChain());
assertThat(request.getAttributeNames().hasMoreElements()).isFalse();
}
@Test
public void doFilterWhenObserveOncePerRequestFalseAndInvokedTwiceThenObserveTwice() throws Throwable {
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
SecurityContextHolder.getContext().setAuthentication(token);
FilterInvocation fi = createinvocation();
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
this.interceptor.invoke(fi);
this.interceptor.invoke(fi);
verify(this.adm, times(2)).decide(any(), any(), any());
}
private FilterInvocation createinvocation() {
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = TestMockHttpServletRequests.get("/secure/page.html").build();
FilterChain chain = mock(FilterChain.class);
FilterInvocation fi = new FilterInvocation(request, response, chain);
return fi;
}
}