diff --git a/web/src/main/java/org/springframework/security/web/intercept/DefaultFilterInvocationSecurityMetadataSource.java b/web/src/main/java/org/springframework/security/web/intercept/DefaultFilterInvocationSecurityMetadataSource.java index 5e90eda615..253dcc57cf 100644 --- a/web/src/main/java/org/springframework/security/web/intercept/DefaultFilterInvocationSecurityMetadataSource.java +++ b/web/src/main/java/org/springframework/security/web/intercept/DefaultFilterInvocationSecurityMetadataSource.java @@ -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 ConfigAttributes that apply to the specified FilterInvocation * or null if no match is found diff --git a/web/src/main/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluator.java b/web/src/main/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluator.java index d0965f7c1b..ae82ecb834 100644 --- a/web/src/main/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluator.java +++ b/web/src/main/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluator.java @@ -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 Authentication 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 Authentication object is + * allowed to invoke the supplied URI, with the given . + *

+ * Note the default implementation of FilterInvocationSecurityMetadataSource disregards the + * contextPath when evaluating which secure object metadata applies to a given + * request URI, so generally the contextPath is unimportant unless you + * are using a custom FilterInvocationSecurityMetadataSource. + * + * @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 Authentication 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 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); } } diff --git a/web/src/main/java/org/springframework/security/web/util/FilterInvocationUtils.java b/web/src/main/java/org/springframework/security/web/util/FilterInvocationUtils.java deleted file mode 100644 index af2bbe9ae3..0000000000 --- a/web/src/main/java/org/springframework/security/web/util/FilterInvocationUtils.java +++ /dev/null @@ -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 FilterInvocations usable within Spring Security.

The generated - * FilterInvocation objects are not intended for use with AbstractSecurityInterceptor - * subclasses. Instead they are generally used by WebInvocationPrivilegeEvaluator.

- * - * @author Ben Alex - * @version $Id$ - */ -public final class FilterInvocationUtils { - //~ Constructors =================================================================================================== - - private FilterInvocationUtils() { - } - - //~ Methods ======================================================================================================== - - /** - * Creates a FilterInvocation for the specified contextPath and Uri. - * Note the normal subclasses of DefaultFilterInvocationSecurityMetadataSource disregard the - * contextPath when evaluating which secure object metadata applies to a given - * FilterInvocation, so generally the contextPath is unimportant unless you are using a - * custom FilterInvocationSecurityMetadataSource. - * - * @param contextPath the contextPath that will be contained within the - * FilterInvocationHttpServletRequest - * @param uri the URI of the request, such as /foo/default.jsp - * - * @return a fully-formed FilterInvocation (never null) - */ - 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 FilterInvocation for the specified Uri. The contextPath - * is set to a default value. - * - * @param uri the URI of the request, such as /foo/default.jsp - * - * @return a fully-formed FilterInvocation (never null) - */ - public static FilterInvocation create(String uri) { - return create("/notused", uri); - } -} diff --git a/web/src/test/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluatorTests.java b/web/src/test/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluatorTests.java index 45c81297dc..0d5b9a0a6b 100644 --- a/web/src/test/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluatorTests.java +++ b/web/src/test/java/org/springframework/security/web/intercept/WebInvocationPrivilegeEvaluatorTests.java @@ -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)); } }