Configure WebInvocationPrivilegeEvaluator bean for multiple filter chains
Closes gh-10554
This commit is contained in:
committed by
Marcus Hert Da Coregio
parent
7e17a00197
commit
18427b6411
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link WebInvocationPrivilegeEvaluator} which delegates to a list of
|
||||
* {@link WebInvocationPrivilegeEvaluator} based on a
|
||||
* {@link org.springframework.security.web.util.matcher.RequestMatcher} evaluation
|
||||
*
|
||||
* @author Marcus Da Coregio
|
||||
* @since 5.7
|
||||
*/
|
||||
public final class RequestMatcherDelegatingWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
|
||||
|
||||
private final List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> delegates;
|
||||
|
||||
public RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries) {
|
||||
Assert.notNull(requestMatcherPrivilegeEvaluatorsEntries, "requestMatcherPrivilegeEvaluators cannot be null");
|
||||
for (RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> entry : requestMatcherPrivilegeEvaluatorsEntries) {
|
||||
Assert.notNull(entry.getRequestMatcher(), "requestMatcher cannot be null");
|
||||
Assert.notNull(entry.getEntry(), "webInvocationPrivilegeEvaluators cannot be null");
|
||||
}
|
||||
this.delegates = requestMatcherPrivilegeEvaluatorsEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user represented by the supplied <tt>Authentication</tt>
|
||||
* object is allowed to invoke the supplied URI.
|
||||
* <p>
|
||||
* Uses the provided URI in the
|
||||
* {@link org.springframework.security.web.util.matcher.RequestMatcher#matches(HttpServletRequest)}
|
||||
* for every {@code RequestMatcher} configured. If no {@code RequestMatcher} is
|
||||
* matched, or if there is not an available {@code WebInvocationPrivilegeEvaluator},
|
||||
* returns {@code true}.
|
||||
* @param uri the URI excluding the context path (a default context path setting will
|
||||
* be used)
|
||||
* @return true if access is allowed, false if denied
|
||||
*/
|
||||
@Override
|
||||
public boolean isAllowed(String uri, Authentication authentication) {
|
||||
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = getDelegate(null, uri, null);
|
||||
if (privilegeEvaluators.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (WebInvocationPrivilegeEvaluator evaluator : privilegeEvaluators) {
|
||||
boolean isAllowed = evaluator.isAllowed(uri, authentication);
|
||||
if (!isAllowed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user represented by the supplied <tt>Authentication</tt>
|
||||
* object is allowed to invoke the supplied URI.
|
||||
* <p>
|
||||
* Uses the provided URI in the
|
||||
* {@link org.springframework.security.web.util.matcher.RequestMatcher#matches(HttpServletRequest)}
|
||||
* for every {@code RequestMatcher} configured. If no {@code RequestMatcher} is
|
||||
* matched, or if there is not an available {@code WebInvocationPrivilegeEvaluator},
|
||||
* returns {@code true}.
|
||||
* @param uri the URI excluding the context path (a default context path setting will
|
||||
* be used)
|
||||
* @param contextPath the context path (may be null, in which case a default value
|
||||
* will be used).
|
||||
* @param method the HTTP method (or null, for any method)
|
||||
* @param authentication the <tt>Authentication</tt> instance whose authorities should
|
||||
* be used in evaluation whether access should be granted.
|
||||
* @return true if access is allowed, false if denied
|
||||
*/
|
||||
@Override
|
||||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
||||
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = getDelegate(contextPath, uri, method);
|
||||
if (privilegeEvaluators.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (WebInvocationPrivilegeEvaluator evaluator : privilegeEvaluators) {
|
||||
boolean isAllowed = evaluator.isAllowed(contextPath, uri, method, authentication);
|
||||
if (!isAllowed) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<WebInvocationPrivilegeEvaluator> getDelegate(String contextPath, String uri, String method) {
|
||||
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method);
|
||||
for (RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate : this.delegates) {
|
||||
if (delegate.getRequestMatcher().matches(filterInvocation.getHttpRequest())) {
|
||||
return delegate.getEntry();
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -67,4 +67,12 @@ public class AuthorizationFilter extends OncePerRequestFilter {
|
||||
return authentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link AuthorizationManager} used by this filter
|
||||
* @return the {@link AuthorizationManager}
|
||||
*/
|
||||
public AuthorizationManager<HttpServletRequest> getAuthorizationManager() {
|
||||
return this.authorizationManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -151,6 +151,10 @@ public final class DebugFilter implements Filter {
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
public FilterChainProxy getFilterChainProxy() {
|
||||
return this.filterChainProxy;
|
||||
}
|
||||
|
||||
static class DebugRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private static final Logger logger = new Logger();
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link RequestMatcherDelegatingWebInvocationPrivilegeEvaluator}
|
||||
*
|
||||
* @author Marcus Da Coregio
|
||||
*/
|
||||
class RequestMatcherDelegatingWebInvocationPrivilegeEvaluatorTests {
|
||||
|
||||
private final RequestMatcher alwaysMatch = mock(RequestMatcher.class);
|
||||
|
||||
private final RequestMatcher alwaysDeny = mock(RequestMatcher.class);
|
||||
|
||||
private final String uri = "/test";
|
||||
|
||||
private final Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
given(this.alwaysMatch.matches(any())).willReturn(true);
|
||||
given(this.alwaysDeny.matches(any())).willReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenDelegatesEmptyThenAllowed() {
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.emptyList());
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenNotMatchThenAllowed() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> notMatch = new RequestMatcherEntry<>(this.alwaysDeny,
|
||||
Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.singletonList(notMatch));
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
|
||||
verify(notMatch.getRequestMatcher()).matches(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenPrivilegeEvaluatorAllowThenAllowedTrue() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
|
||||
this.alwaysMatch, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.singletonList(delegate));
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenPrivilegeEvaluatorDenyThenAllowedFalse() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
|
||||
this.alwaysMatch, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysDeny()));
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.singletonList(delegate));
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenNotMatchThenMatchThenOnlySecondDelegateInvoked() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> notMatchDelegate = new RequestMatcherEntry<>(
|
||||
this.alwaysDeny, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> matchDelegate = new RequestMatcherEntry<>(
|
||||
this.alwaysMatch, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> spyNotMatchDelegate = spy(notMatchDelegate);
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> spyMatchDelegate = spy(matchDelegate);
|
||||
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Arrays.asList(notMatchDelegate, spyMatchDelegate));
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
|
||||
verify(spyNotMatchDelegate.getRequestMatcher()).matches(any());
|
||||
verify(spyNotMatchDelegate, never()).getEntry();
|
||||
verify(spyMatchDelegate.getRequestMatcher()).matches(any());
|
||||
verify(spyMatchDelegate, times(2)).getEntry(); // 2 times, one for constructor and
|
||||
// other one in isAllowed
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenDelegatePrivilegeEvaluatorsEmptyThenAllowedTrue() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
|
||||
this.alwaysMatch, Collections.emptyList());
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.singletonList(delegate));
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenFirstDelegateDenyThenDoNotInvokeOthers() {
|
||||
WebInvocationPrivilegeEvaluator deny = TestWebInvocationPrivilegeEvaluator.alwaysDeny();
|
||||
WebInvocationPrivilegeEvaluator allow = TestWebInvocationPrivilegeEvaluator.alwaysAllow();
|
||||
WebInvocationPrivilegeEvaluator spyDeny = spy(deny);
|
||||
WebInvocationPrivilegeEvaluator spyAllow = spy(allow);
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
|
||||
this.alwaysMatch, Arrays.asList(spyDeny, spyAllow));
|
||||
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.singletonList(delegate));
|
||||
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isFalse();
|
||||
verify(spyDeny).isAllowed(any(), any());
|
||||
verifyNoInteractions(spyAllow);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenDifferentArgumentsThenCallSpecificIsAllowedInDelegate() {
|
||||
WebInvocationPrivilegeEvaluator deny = TestWebInvocationPrivilegeEvaluator.alwaysDeny();
|
||||
WebInvocationPrivilegeEvaluator spyDeny = spy(deny);
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
|
||||
this.alwaysMatch, Collections.singletonList(spyDeny));
|
||||
|
||||
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
Collections.singletonList(delegate));
|
||||
|
||||
assertThat(delegating.isAllowed(this.uri, this.authentication)).isFalse();
|
||||
assertThat(delegating.isAllowed("/cp", this.uri, "GET", this.authentication)).isFalse();
|
||||
verify(spyDeny).isAllowed(any(), any());
|
||||
verify(spyDeny).isAllowed(any(), any(), any(), any());
|
||||
verifyNoMoreInteractions(spyDeny);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWhenPrivilegeEvaluatorsNullThenException() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> entry = new RequestMatcherEntry<>(this.alwaysMatch,
|
||||
null);
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(Collections.singletonList(entry)))
|
||||
.withMessageContaining("webInvocationPrivilegeEvaluators cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWhenRequestMatcherNullThenException() {
|
||||
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> entry = new RequestMatcherEntry<>(null,
|
||||
Collections.singletonList(mock(WebInvocationPrivilegeEvaluator.class)));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(Collections.singletonList(entry)))
|
||||
.withMessageContaining("requestMatcher cannot be null");
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.springframework.security.core.Authentication;
|
||||
|
||||
public final class TestWebInvocationPrivilegeEvaluator {
|
||||
|
||||
private static final AlwaysAllowWebInvocationPrivilegeEvaluator ALWAYS_ALLOW = new AlwaysAllowWebInvocationPrivilegeEvaluator();
|
||||
|
||||
private static final AlwaysDenyWebInvocationPrivilegeEvaluator ALWAYS_DENY = new AlwaysDenyWebInvocationPrivilegeEvaluator();
|
||||
|
||||
private TestWebInvocationPrivilegeEvaluator() {
|
||||
}
|
||||
|
||||
public static WebInvocationPrivilegeEvaluator alwaysAllow() {
|
||||
return ALWAYS_ALLOW;
|
||||
}
|
||||
|
||||
public static WebInvocationPrivilegeEvaluator alwaysDeny() {
|
||||
return ALWAYS_DENY;
|
||||
}
|
||||
|
||||
private static class AlwaysAllowWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String uri, Authentication authentication) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class AlwaysDenyWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String uri, Authentication authentication) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+8
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -125,4 +125,11 @@ public class AuthorizationFilterTests {
|
||||
verifyNoInteractions(mockFilterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAuthorizationManager() {
|
||||
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
|
||||
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
|
||||
assertThat(authorizationFilter.getAuthorizationManager()).isSameAs(authorizationManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user