9 changed files with 633 additions and 14 deletions
@ -0,0 +1,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(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,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"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue