SEC-335: Support for ANY_CHANNEL configuration attribute in channel processing. Also added to namespace.

This commit is contained in:
Luke Taylor 2008-01-17 20:52:26 +00:00
parent 2ed1c7d494
commit ea70845987
5 changed files with 73 additions and 48 deletions

View File

@ -60,6 +60,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
static final String ATT_ACCESS_CONFIG = "access"; static final String ATT_ACCESS_CONFIG = "access";
static final String ATT_REQUIRES_CHANNEL = "requires-channel"; 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 ATT_CREATE_SESSION = "create-session";
static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired"; static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired";
@ -176,7 +179,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource", channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource",
channelFilterInvDefSource); channelFilterInvDefSource);
RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class); RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class);
ManagedList channelProcessors = new ManagedList(2); ManagedList channelProcessors = new ManagedList(3);
RootBeanDefinition secureChannelProcessor = new RootBeanDefinition(SecureChannelProcessor.class); RootBeanDefinition secureChannelProcessor = new RootBeanDefinition(SecureChannelProcessor.class);
RootBeanDefinition retryWithHttp = new RootBeanDefinition(RetryWithHttpEntryPoint.class); RootBeanDefinition retryWithHttp = new RootBeanDefinition(RetryWithHttpEntryPoint.class);
RootBeanDefinition retryWithHttps = new RootBeanDefinition(RetryWithHttpsEntryPoint.class); RootBeanDefinition retryWithHttps = new RootBeanDefinition(RetryWithHttpsEntryPoint.class);
@ -277,10 +280,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (StringUtils.hasText(requiredChannel)) { if (StringUtils.hasText(requiredChannel)) {
String channelConfigAttribute = null; String channelConfigAttribute = null;
if (requiredChannel.equals("https")) { if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) {
channelConfigAttribute = "REQUIRES_SECURE_CHANNEL"; channelConfigAttribute = "REQUIRES_SECURE_CHANNEL";
} else if (requiredChannel.equals("http")) { } else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) {
channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL"; channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL";
} else if (requiredChannel.equals(OPT_ANY_CHANNEL)) {
channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL;
} else { } else {
parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt); parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt);
} }

View File

@ -21,6 +21,7 @@ import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException; import java.io.IOException;
@ -31,16 +32,25 @@ import javax.servlet.ServletException;
/** /**
* Implementation of {@link ChannelDecisionManager}.<p>Iterates through each configured {@link ChannelProcessor}. * Implementation of {@link ChannelDecisionManager}.
* If a <code>ChannelProcessor</code> has any issue with the security of the request, it should cause a redirect, * <p>
* exception or whatever other action is appropriate for the <code>ChannelProcessor</code> implementation.</p> * Iterates through each configured {@link ChannelProcessor}. If a <code>ChannelProcessor</code> has any issue with the
* <P>Once any response is committed (ie a redirect is written to the response object), the * security of the request, it should cause a redirect, exception or whatever other action is appropriate for the
* <code>ChannelDecisionManagerImpl</code> will not iterate through any further <code>ChannelProcessor</code>s.</p> * <code>ChannelProcessor</code> implementation.
* <p>
* Once any response is committed (ie a redirect is written to the response object), the
* <code>ChannelDecisionManagerImpl</code> will not iterate through any further <code>ChannelProcessor</code>s.
* <p>
* 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 * @author Ben Alex
* @version $Id$ * @version $Id$
*/ */
public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean { public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean {
public static final String ANY_CHANNEL = "ANY_CHANNEL";
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
private List channelProcessors; private List channelProcessors;
@ -52,13 +62,21 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi
} }
private void checkIfValidList(List listToCheck) { private void checkIfValidList(List listToCheck) {
if ((listToCheck == null) || (listToCheck.size() == 0)) { Assert.notEmpty(listToCheck, "A list of ChannelProcessors is required");
throw new IllegalArgumentException("A list of ChannelProcessors is required");
}
} }
public void decide(FilterInvocation invocation, ConfigAttributeDefinition config) 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(); Iterator iter = this.channelProcessors.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
@ -72,7 +90,7 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi
} }
} }
public List getChannelProcessors() { protected List getChannelProcessors() {
return this.channelProcessors; return this.channelProcessors;
} }
@ -82,22 +100,19 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi
Iterator iter = newList.iterator(); Iterator iter = newList.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Object currentObject = null; Object currentObject = iter.next();
Assert.isInstanceOf(ChannelProcessor.class, currentObject, "ChannelProcessor " +
try { currentObject.getClass().getName() + " must implement ChannelProcessor");
currentObject = iter.next();
ChannelProcessor attemptToCast = (ChannelProcessor) currentObject;
} catch (ClassCastException cce) {
throw new IllegalArgumentException("ChannelProcessor " + currentObject.getClass().getName()
+ " must implement ChannelProcessor");
}
} }
this.channelProcessors = newList; this.channelProcessors = newList;
} }
public boolean supports(ConfigAttribute attribute) { public boolean supports(ConfigAttribute attribute) {
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
return true;
}
Iterator iter = this.channelProcessors.iterator(); Iterator iter = this.channelProcessors.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {

View File

@ -135,7 +135,7 @@ intercept-url.attlist &=
attribute filters {"none"}? attribute filters {"none"}?
intercept-url.attlist &= intercept-url.attlist &=
## Used to specify that a URL must be accessed over http or https ## 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 = 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. ## 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.

View File

@ -368,6 +368,7 @@
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="http"/> <xs:enumeration value="http"/>
<xs:enumeration value="https"/> <xs:enumeration value="https"/>
<xs:enumeration value="any"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:attribute> </xs:attribute>

View File

@ -45,16 +45,7 @@ import javax.servlet.ServletException;
public class ChannelDecisionManagerImplTests extends TestCase { public class ChannelDecisionManagerImplTests extends TestCase {
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
public static void main(String[] args) { public void testCannotSetEmptyChannelProcessorsList() throws Exception {
junit.textui.TestRunner.run(ChannelDecisionManagerImplTests.class);
}
public final void setUp() throws Exception {
super.setUp();
}
public void testCannotSetEmptyChannelProcessorsList()
throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
try { try {
@ -65,8 +56,7 @@ public class ChannelDecisionManagerImplTests extends TestCase {
} }
} }
public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() throws Exception {
throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
List list = new Vector(); List list = new Vector();
list.add("THIS IS NOT A CHANNELPROCESSOR"); list.add("THIS IS NOT A CHANNELPROCESSOR");
@ -79,8 +69,7 @@ public class ChannelDecisionManagerImplTests extends TestCase {
} }
} }
public void testCannotSetNullChannelProcessorsList() public void testCannotSetNullChannelProcessorsList() throws Exception {
throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
try { try {
@ -113,8 +102,28 @@ public class ChannelDecisionManagerImplTests extends TestCase {
assertTrue(fi.getResponse().isCommitted()); assertTrue(fi.getResponse().isCommitted());
} }
public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() public void testAnyChannelAttributeCausesProcessorsToBeSkipped() throws Exception {
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(); ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
@ -165,8 +174,7 @@ public class ChannelDecisionManagerImplTests extends TestCase {
assertEquals(list, cdm.getChannelProcessors()); assertEquals(list, cdm.getChannelProcessors());
} }
public void testStartupFailsWithEmptyChannelProcessorsList() public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception {
throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
try { try {
@ -188,12 +196,8 @@ public class ChannelDecisionManagerImplTests extends TestCase {
this.failIfCalled = failIfCalled; this.failIfCalled = failIfCalled;
} }
private MockChannelProcessor() {
super();
}
public void decide(FilterInvocation invocation, ConfigAttributeDefinition config) public void decide(FilterInvocation invocation, ConfigAttributeDefinition config)
throws IOException, ServletException { throws IOException, ServletException {
Iterator iter = config.getConfigAttributes(); Iterator iter = config.getConfigAttributes();
if (failIfCalled) { if (failIfCalled) {