diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index d949ae0319..e9f42dd1f8 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -60,6 +60,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String ATT_ACCESS_CONFIG = "access"; static final String ATT_REQUIRES_CHANNEL = "requires-channel"; + static final String OPT_REQUIRES_HTTP = "http"; + static final String OPT_REQUIRES_HTTPS = "https"; + static final String OPT_ANY_CHANNEL = "any"; static final String ATT_CREATE_SESSION = "create-session"; static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired"; @@ -176,12 +179,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource", channelFilterInvDefSource); RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class); - ManagedList channelProcessors = new ManagedList(2); + ManagedList channelProcessors = new ManagedList(3); RootBeanDefinition secureChannelProcessor = new RootBeanDefinition(SecureChannelProcessor.class); RootBeanDefinition retryWithHttp = new RootBeanDefinition(RetryWithHttpEntryPoint.class); RootBeanDefinition retryWithHttps = new RootBeanDefinition(RetryWithHttpsEntryPoint.class); retryWithHttp.getPropertyValues().addPropertyValue("portMapper", portMapperRef); - retryWithHttps.getPropertyValues().addPropertyValue("portMapper", portMapperRef); + retryWithHttps.getPropertyValues().addPropertyValue("portMapper", portMapperRef); secureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint", retryWithHttps); RootBeanDefinition inSecureChannelProcessor = new RootBeanDefinition(InsecureChannelProcessor.class); inSecureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint", retryWithHttp); @@ -277,10 +280,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { if (StringUtils.hasText(requiredChannel)) { String channelConfigAttribute = null; - if (requiredChannel.equals("https")) { + if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) { channelConfigAttribute = "REQUIRES_SECURE_CHANNEL"; - } else if (requiredChannel.equals("http")) { + } else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) { channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL"; + } else if (requiredChannel.equals(OPT_ANY_CHANNEL)) { + channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL; } else { parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt); } diff --git a/core/src/main/java/org/springframework/security/securechannel/ChannelDecisionManagerImpl.java b/core/src/main/java/org/springframework/security/securechannel/ChannelDecisionManagerImpl.java index 41698cf8b4..603cd17b62 100644 --- a/core/src/main/java/org/springframework/security/securechannel/ChannelDecisionManagerImpl.java +++ b/core/src/main/java/org/springframework/security/securechannel/ChannelDecisionManagerImpl.java @@ -21,6 +21,7 @@ import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; import java.io.IOException; @@ -31,16 +32,25 @@ import javax.servlet.ServletException; /** - * Implementation of {@link ChannelDecisionManager}.

Iterates through each configured {@link ChannelProcessor}. - * If a ChannelProcessor has any issue with the security of the request, it should cause a redirect, - * exception or whatever other action is appropriate for the ChannelProcessor implementation.

- *

Once any response is committed (ie a redirect is written to the response object), the - * ChannelDecisionManagerImpl will not iterate through any further ChannelProcessors.

+ * Implementation of {@link ChannelDecisionManager}. + *

+ * Iterates through each configured {@link ChannelProcessor}. If a ChannelProcessor has any issue with the + * security of the request, it should cause a redirect, exception or whatever other action is appropriate for the + * ChannelProcessor implementation. + *

+ * Once any response is committed (ie a redirect is written to the response object), the + * ChannelDecisionManagerImpl will not iterate through any further ChannelProcessors. + *

+ * The attribute "ANY_CHANNEL" if applied to a particular URL, the iteration through the channel processors will be + * skipped (see SEC-494, SEC-335). * * @author Ben Alex * @version $Id$ */ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean { + + public static final String ANY_CHANNEL = "ANY_CHANNEL"; + //~ Instance fields ================================================================================================ private List channelProcessors; @@ -52,13 +62,21 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi } private void checkIfValidList(List listToCheck) { - if ((listToCheck == null) || (listToCheck.size() == 0)) { - throw new IllegalArgumentException("A list of ChannelProcessors is required"); - } + Assert.notEmpty(listToCheck, "A list of ChannelProcessors is required"); } public void decide(FilterInvocation invocation, ConfigAttributeDefinition config) - throws IOException, ServletException { + throws IOException, ServletException { + + Iterator attrs = config.getConfigAttributes(); + + while (attrs.hasNext()) { + ConfigAttribute attribute = (ConfigAttribute) attrs.next(); + if (ANY_CHANNEL.equals(attribute.getAttribute())) { + return; + } + } + Iterator iter = this.channelProcessors.iterator(); while (iter.hasNext()) { @@ -72,7 +90,7 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi } } - public List getChannelProcessors() { + protected List getChannelProcessors() { return this.channelProcessors; } @@ -82,22 +100,19 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi Iterator iter = newList.iterator(); while (iter.hasNext()) { - Object currentObject = null; - - try { - currentObject = iter.next(); - - ChannelProcessor attemptToCast = (ChannelProcessor) currentObject; - } catch (ClassCastException cce) { - throw new IllegalArgumentException("ChannelProcessor " + currentObject.getClass().getName() - + " must implement ChannelProcessor"); - } + Object currentObject = iter.next(); + Assert.isInstanceOf(ChannelProcessor.class, currentObject, "ChannelProcessor " + + currentObject.getClass().getName() + " must implement ChannelProcessor"); } this.channelProcessors = newList; } public boolean supports(ConfigAttribute attribute) { + if (ANY_CHANNEL.equals(attribute.getAttribute())) { + return true; + } + Iterator iter = this.channelProcessors.iterator(); while (iter.hasNext()) { diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc index 46d6db99a6..75fdfcb36b 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc @@ -135,7 +135,7 @@ intercept-url.attlist &= attribute filters {"none"}? intercept-url.attlist &= ## Used to specify that a URL must be accessed over http or https - attribute requires-channel {"http" | "https"}? + attribute requires-channel {"http" | "https" | "any"}? logout = ## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic. diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd index 538659da76..20a71215e8 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd @@ -368,6 +368,7 @@ + diff --git a/core/src/test/java/org/springframework/security/securechannel/ChannelDecisionManagerImplTests.java b/core/src/test/java/org/springframework/security/securechannel/ChannelDecisionManagerImplTests.java index 2f19c82b79..b4c5383b13 100644 --- a/core/src/test/java/org/springframework/security/securechannel/ChannelDecisionManagerImplTests.java +++ b/core/src/test/java/org/springframework/security/securechannel/ChannelDecisionManagerImplTests.java @@ -45,16 +45,7 @@ import javax.servlet.ServletException; public class ChannelDecisionManagerImplTests extends TestCase { //~ Methods ======================================================================================================== - public static void main(String[] args) { - junit.textui.TestRunner.run(ChannelDecisionManagerImplTests.class); - } - - public final void setUp() throws Exception { - super.setUp(); - } - - public void testCannotSetEmptyChannelProcessorsList() - throws Exception { + public void testCannotSetEmptyChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); try { @@ -65,8 +56,7 @@ public class ChannelDecisionManagerImplTests extends TestCase { } } - public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() - throws Exception { + public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); List list = new Vector(); list.add("THIS IS NOT A CHANNELPROCESSOR"); @@ -79,8 +69,7 @@ public class ChannelDecisionManagerImplTests extends TestCase { } } - public void testCannotSetNullChannelProcessorsList() - throws Exception { + public void testCannotSetNullChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); try { @@ -113,8 +102,28 @@ public class ChannelDecisionManagerImplTests extends TestCase { assertTrue(fi.getResponse().isCommitted()); } - public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() - throws Exception { + 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(); + MockFilterChain chain = new MockFilterChain(); + FilterInvocation fi = new FilterInvocation(request, response, chain); + + ConfigAttributeDefinition cad = new ConfigAttributeDefinition(); + cad.addConfigAttribute(new SecurityConfig("abc")); + cad.addConfigAttribute(new SecurityConfig("ANY_CHANNEL")); + + cdm.decide(fi, cad); + assertFalse(fi.getResponse().isCommitted()); + } + + public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); @@ -165,8 +174,7 @@ public class ChannelDecisionManagerImplTests extends TestCase { assertEquals(list, cdm.getChannelProcessors()); } - public void testStartupFailsWithEmptyChannelProcessorsList() - throws Exception { + public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); try { @@ -188,12 +196,8 @@ public class ChannelDecisionManagerImplTests extends TestCase { this.failIfCalled = failIfCalled; } - private MockChannelProcessor() { - super(); - } - public void decide(FilterInvocation invocation, ConfigAttributeDefinition config) - throws IOException, ServletException { + throws IOException, ServletException { Iterator iter = config.getConfigAttributes(); if (failIfCalled) {