SEC-1149: WebInvocationPrivilegeEvaluator now contains methods to evaluate the permissions on URIs directly. Deleted FilterInvocationUtils.

This commit is contained in:
Luke Taylor 2009-05-02 06:02:17 +00:00
parent d1cb85e4f3
commit dca566ff1f
4 changed files with 110 additions and 156 deletions

View File

@ -158,7 +158,7 @@ public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvo
* Subclasses can override if required to perform any modifications to the URL.
*
* @param url the URI to retrieve configuration attributes for
* @param method the HTTP method (GET, POST, DELETE...).
* @param method the HTTP method (GET, POST, DELETE...), or null for any method.
*
* @return the <code>ConfigAttribute</code>s that apply to the specified <code>FilterInvocation</code>
* or null if no match is found

View File

@ -15,11 +15,18 @@
package org.springframework.security.web.intercept;
import java.io.IOException;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
@ -34,24 +41,69 @@ import org.springframework.util.Assert;
* @author Ben Alex
* @version $Id$
*/
public class WebInvocationPrivilegeEvaluator implements InitializingBean {
public class WebInvocationPrivilegeEvaluator {
//~ Static fields/initializers =====================================================================================
protected static final Log logger = LogFactory.getLog(WebInvocationPrivilegeEvaluator.class);
static final FilterChain DUMMY_CHAIN = new FilterChain() {
public void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException {
throw new UnsupportedOperationException("WebInvocationPrivilegeEvaluator does not support filter chains");
}
};
//~ Instance fields ================================================================================================
private AbstractSecurityInterceptor securityInterceptor;
//~ Methods ========================================================================================================
//~ Constructors ===================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(securityInterceptor, "SecurityInterceptor required");
public WebInvocationPrivilegeEvaluator(AbstractSecurityInterceptor securityInterceptor) {
Assert.notNull(securityInterceptor, "SecurityInterceptor cannot be null");
Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()),
"AbstractSecurityInterceptor does not support FilterInvocations");
Assert.notNull(securityInterceptor.getAccessDecisionManager(),
"AbstractSecurityInterceptor must provide a non-null AccessDecisionManager");
this.securityInterceptor = securityInterceptor;
}
public boolean isAllowed(FilterInvocation fi, Authentication authentication) {
Assert.notNull(fi, "FilterInvocation required");
//~ Methods ========================================================================================================
/**
* Determines whether the user represented by the supplied <tt>Authentication</tt> object is
* allowed to invoke the supplied URI.
*
* @param uri the URI excluding the context path (a default context path setting will be used)
*/
public boolean isAllowed(String uri, Authentication authentication) {
return isAllowed(null, uri, null, authentication);
}
/**
* Determines whether the user represented by the supplied <tt>Authentication</tt> object is
* allowed to invoke the supplied URI, with the given .
* <p>
* Note the default implementation of <tt>FilterInvocationSecurityMetadataSource</tt> disregards the
* <code>contextPath</code> when evaluating which secure object metadata applies to a given
* request URI, so generally the <code>contextPath</code> is unimportant unless you
* are using a custom <code>FilterInvocationSecurityMetadataSource</code>.
*
* @param uri the URI excluding the context path
* @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
*/
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
Assert.notNull(uri, "uri parameter is required");
if (contextPath == null) {
contextPath = "/ctxpath";
}
FilterInvocation fi = createFilterInvocation(contextPath, uri, method);
List<ConfigAttribute> attrs = securityInterceptor.obtainSecurityMetadataSource().getAttributes(fi);
if (attrs == null) {
@ -63,7 +115,7 @@ public class WebInvocationPrivilegeEvaluator implements InitializingBean {
}
if ((authentication == null) || (authentication.getAuthorities() == null)
|| authentication.getAuthorities().isEmpty()) {
|| authentication.getAuthorities().isEmpty()) {
return false;
}
@ -80,12 +132,16 @@ public class WebInvocationPrivilegeEvaluator implements InitializingBean {
return true;
}
public void setSecurityInterceptor(AbstractSecurityInterceptor securityInterceptor) {
Assert.notNull(securityInterceptor, "AbstractSecurityInterceptor cannot be null");
Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()),
"AbstractSecurityInterceptor does not support FilterInvocations");
Assert.notNull(securityInterceptor.getAccessDecisionManager(),
"AbstractSecurityInterceptor must provide a non-null AccessDecisionManager");
this.securityInterceptor = securityInterceptor;
private FilterInvocation createFilterInvocation(String contextPath, String uri, String method) {
Assert.hasText(contextPath, "contextPath required");
Assert.hasText(uri, "URI required");
MockHttpServletRequest req = new MockHttpServletRequest();
req.setRequestURI(contextPath + uri);
req.setContextPath(contextPath);
req.setServletPath(null);
req.setMethod(method);
return new FilterInvocation(req, new MockHttpServletResponse(), DUMMY_CHAIN);
}
}

View File

@ -1,91 +0,0 @@
/* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.util;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.Assert;
/**
* Static utility methods for creating <code>FilterInvocation</code>s usable within Spring Security.<p>The generated
* <code>FilterInvocation</code> objects are not intended for use with <code>AbstractSecurityInterceptor</code>
* subclasses. Instead they are generally used by <code>WebInvocationPrivilegeEvaluator</code>.</p>
*
* @author Ben Alex
* @version $Id$
*/
public final class FilterInvocationUtils {
//~ Constructors ===================================================================================================
private FilterInvocationUtils() {
}
//~ Methods ========================================================================================================
/**
* Creates a <code>FilterInvocation</code> for the specified <code>contextPath</code> and <code>Uri</code>.
* Note the normal subclasses of <tt>DefaultFilterInvocationSecurityMetadataSource</tt> disregard the
* <code>contextPath</code> when evaluating which secure object metadata applies to a given
* <code>FilterInvocation</code>, so generally the <code>contextPath</code> is unimportant unless you are using a
* custom <code>FilterInvocationSecurityMetadataSource</code>.
*
* @param contextPath the <code>contextPath</code> that will be contained within the
* <code>FilterInvocation</code><code>HttpServletRequest</code>
* @param uri the URI of the request, such as <code>/foo/default.jsp</code>
*
* @return a fully-formed <code>FilterInvocation</code> (never <code>null</code>)
*/
public static FilterInvocation create(String contextPath, String uri) {
Assert.hasText(contextPath, "contextPath required");
Assert.hasText(uri, "URI required");
MockHttpServletRequest req = new MockHttpServletRequest();
req.setRequestURI(contextPath + uri);
req.setContextPath(contextPath);
req.setServletPath(null);
FilterInvocation fi = new FilterInvocation(req, new MockHttpServletResponse(),
new FilterChain() {
public void doFilter(ServletRequest arg0, ServletResponse arg1) throws IOException, ServletException {
throw new UnsupportedOperationException(
"WebInvocationPrivilegeEvaluator does not support filter chains");
}
});
return fi;
}
/**
* Creates a <code>FilterInvocation</code> for the specified <code>Uri</code>. The <code>contextPath</code>
* is set to a default value.
*
* @param uri the URI of the request, such as <code>/foo/default.jsp</code>
*
* @return a fully-formed <code>FilterInvocation</code> (never <code>null</code>)
*/
public static FilterInvocation create(String uri) {
return create("/notused", uri);
}
}

View File

@ -15,16 +15,13 @@
package org.springframework.security.web.intercept;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.security.matcher.AuthenticationMatcher.anAuthenticationWithUsername;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.MockApplicationEventPublisher;
@ -35,11 +32,6 @@ 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.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.intercept.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.util.FilterInvocationUtils;
/**
@ -49,8 +41,6 @@ import org.springframework.security.web.util.FilterInvocationUtils;
* @version $Id$
*/
public class WebInvocationPrivilegeEvaluatorTests {
private Mockery jmock = new JUnit4Mockery();
private AuthenticationManager am;
private AccessDecisionManager adm;
private FilterInvocationSecurityMetadataSource ods;
private RunAsManager ram;
@ -59,13 +49,12 @@ public class WebInvocationPrivilegeEvaluatorTests {
//~ Methods ========================================================================================================
@Before
public final void setUp() throws Exception {
public final void setUp() {
interceptor = new FilterSecurityInterceptor();
am = jmock.mock(AuthenticationManager.class);
ods = jmock.mock(FilterInvocationSecurityMetadataSource.class);
adm = jmock.mock(AccessDecisionManager.class);
ram = jmock.mock(RunAsManager.class);
interceptor.setAuthenticationManager(am);
ods = mock(FilterInvocationSecurityMetadataSource.class);
adm = mock(AccessDecisionManager.class);
ram = mock(RunAsManager.class);
interceptor.setAuthenticationManager(mock(AuthenticationManager.class));
interceptor.setSecurityMetadataSource(ods);
interceptor.setAccessDecisionManager(adm);
interceptor.setRunAsManager(ram);
@ -73,47 +62,47 @@ public class WebInvocationPrivilegeEvaluatorTests {
SecurityContextHolder.clearContext();
}
@After
public void tearDown() throws Exception {
SecurityContextHolder.clearContext();
@Test
public void permitsAccessIfNoMatchingAttributesAndPublicInvocationsAllowed() throws Exception {
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator(interceptor);
when(ods.getAttributes(anyObject())).thenReturn(null);
assertTrue(wipe.isAllowed("/context", "/foo/index.jsp", "GET", mock(Authentication.class)));
}
@Test
public void deniesAccessIfNoMatchingAttributesAndPublicInvocationsNotAllowed() throws Exception {
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator(interceptor);
when(ods.getAttributes(anyObject())).thenReturn(null);
interceptor.setRejectPublicInvocations(true);
assertFalse(wipe.isAllowed("/context", "/foo/index.jsp", "GET", mock(Authentication.class)));
}
@Test
public void deniesAccessIfAuthenticationIsNull() throws Exception {
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator(interceptor);
assertFalse(wipe.isAllowed("/foo/index.jsp", null));
}
@SuppressWarnings("unchecked")
@Test
public void allowsAccessIfAccessDecisionMangerDoes() throws Exception {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
FilterInvocation fi = FilterInvocationUtils.create("/foo/index.jsp");
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator();
wipe.setSecurityInterceptor(interceptor);
wipe.afterPropertiesSet();
jmock.checking(new Expectations() {{
ignoring(ram); ignoring(ods);
oneOf(adm).decide(with(anAuthenticationWithUsername("test")), with(anything()), with(aNonNull(List.class)));
}});
assertTrue(wipe.isAllowed(fi, token));
jmock.assertIsSatisfied();
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator(interceptor);
assertTrue(wipe.isAllowed("/foo/index.jsp", token));
}
@SuppressWarnings("unchecked")
@Test
public void deniesAccessIfAccessDecisionMangerDoes() throws Exception {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
FilterInvocation fi = FilterInvocationUtils.create("/foo/index.jsp");
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator(interceptor);
WebInvocationPrivilegeEvaluator wipe = new WebInvocationPrivilegeEvaluator();
wipe.setSecurityInterceptor(interceptor);
wipe.afterPropertiesSet();
doThrow(new AccessDeniedException("")).when(adm).decide(any(Authentication.class), anyObject(), anyList());
jmock.checking(new Expectations() {{
ignoring(ram); ignoring(ods);
oneOf(adm).decide(with(anAuthenticationWithUsername("test")), with(anything()), with(aNonNull(List.class)));
will(throwException(new AccessDeniedException("")));
}});
assertFalse(wipe.isAllowed("/foo/index.jsp", token));
}
assertFalse(wipe.isAllowed(fi, token));
jmock.assertIsSatisfied();
@Test(expected=UnsupportedOperationException.class)
public void dummyChainRejectsInvocation() throws Exception {
WebInvocationPrivilegeEvaluator.DUMMY_CHAIN.doFilter(mock(HttpServletRequest.class), mock(HttpServletResponse.class));
}
}