浏览代码

Make ChannelDecisionManagerImpl iterate through a list of channel security processors.

Ben Alex 21 年之前
父节点
当前提交
ecac5a2eed
共有 19 个文件被更改,包括 1122 次插入536 次删除
  1. 2 2
      changelog.txt
  2. 30 2
      core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManager.java
  3. 63 59
      core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManagerImpl.java
  4. 7 8
      core/src/main/java/org/acegisecurity/securechannel/ChannelEntryPoint.java
  5. 45 44
      core/src/main/java/org/acegisecurity/securechannel/ChannelProcessingFilter.java
  6. 72 0
      core/src/main/java/org/acegisecurity/securechannel/ChannelProcessor.java
  7. 117 0
      core/src/main/java/org/acegisecurity/securechannel/InsecureChannelProcessor.java
  8. 0 50
      core/src/main/java/org/acegisecurity/securechannel/InsecureChannelRequiredException.java
  9. 116 0
      core/src/main/java/org/acegisecurity/securechannel/SecureChannelProcessor.java
  10. 0 50
      core/src/main/java/org/acegisecurity/securechannel/SecureChannelRequiredException.java
  11. 5 1
      core/src/test/java/org/acegisecurity/MockHttpServletResponse.java
  12. 129 86
      core/src/test/java/org/acegisecurity/securechannel/ChannelDecisionManagerImplTests.java
  13. 139 131
      core/src/test/java/org/acegisecurity/securechannel/ChannelProcessingFilterTests.java
  14. 153 0
      core/src/test/java/org/acegisecurity/securechannel/InsecureChannelProcessorTests.java
  15. 153 0
      core/src/test/java/org/acegisecurity/securechannel/SecureChannelProcessorTests.java
  16. 71 59
      docs/reference/src/index.xml
  17. 9 20
      samples/contacts/etc/cas/applicationContext.xml
  18. 9 22
      samples/contacts/etc/filter/applicationContext.xml
  19. 2 2
      samples/contacts/etc/filter/web.xml

+ 2 - 2
changelog.txt

@@ -1,4 +1,4 @@
-Changes in version 0.5 (2004-04-28)
+Changes in version 0.5 (2004-04-29)
 -----------------------------------
 
 * Added single sign on support via Yale Central Authentication Service (CAS)
@@ -13,7 +13,7 @@ Changes in version 0.5 (2004-04-28)
 * Added definable prefixes to avoid expectation of "ROLE_" GrantedAuthoritys
 * Added pluggable AuthenticationEntryPoints to SecurityEnforcementFilter
 * Added Apache Ant path syntax support to SecurityEnforcementFilter
-* Added filter to automate entry into secure channels, such as HTTPS
+* Added filter to automate web channel requirements (eg HTTPS redirection)
 * Updated JAR to Spring 1.0.1
 * Updated several classes to use absolute (not relative) redirection URLs
 * Refactored filters to use Spring application context lifecycle support

+ 30 - 2
core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManager.java

@@ -15,12 +15,23 @@
 
 package net.sf.acegisecurity.securechannel;
 
+import net.sf.acegisecurity.ConfigAttribute;
 import net.sf.acegisecurity.ConfigAttributeDefinition;
 import net.sf.acegisecurity.intercept.web.FilterInvocation;
 
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
 
 /**
  * Decides whether a web channel provides sufficient security.
+ * 
+ * <P>
+ * If necessary due to the nature of the redirection, implementations should
+ * store the original destination of the request in {@link
+ * net.sf.acegisecurity.ui.AbstractProcessingFilter#ACEGI_SECURITY_TARGET_URL_KEY}.
+ * </p>
  *
  * @author Ben Alex
  * @version $Id$
@@ -34,6 +45,23 @@ public interface ChannelDecisionManager {
      * ConfigAttributeDefinition}.
      */
     public void decide(FilterInvocation invocation,
-        ConfigAttributeDefinition config)
-        throws InsecureChannelRequiredException, SecureChannelRequiredException;
+        ConfigAttributeDefinition config) throws IOException, ServletException;
+
+    /**
+     * Indicates whether this <code>ChannelDecisionManager</code> is able to
+     * process the passed <code>ConfigAttribute</code>.
+     * 
+     * <p>
+     * This allows the <code>ChannelProcessingFilter</code> to check every
+     * configuration attribute can be consumed by the configured
+     * <code>ChannelDecisionManager</code>.
+     * </p>
+     *
+     * @param attribute a configuration attribute that has been configured
+     *        against the <code>ChannelProcessingFilter</code>
+     *
+     * @return true if this <code>ChannelDecisionManager</code> can support the
+     *         passed configuration attribute
+     */
+    public boolean supports(ConfigAttribute attribute);
 }

+ 63 - 59
core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManagerImpl.java

@@ -21,100 +21,104 @@ import net.sf.acegisecurity.intercept.web.FilterInvocation;
 
 import org.springframework.beans.factory.InitializingBean;
 
+import java.io.IOException;
+
 import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.ServletException;
 
 
 /**
- * <p>
- * Ensures configuration attribute requested channel security is present by
- * review of <code>HttpServletRequest.isSecure()</code> responses.
- * </p>
+ * Implementation of {@link ChannelDecisionManager}.
  * 
- * <P>
- * The class responds to two and only two case-sensitive keywords: {@link
- * #getInsecureKeyword()} and {@link #getSecureKeyword}. If either of these
- * keywords are detected, <code>HttpServletRequest.isSecure()</code> is used
- * to determine the channel security offered. If the channel security differs
- * from that requested by the keyword, the relevant exception is thrown.
- * </p>
- * 
- * <P>
- * If both the <code>secureKeyword</code> and <code>insecureKeyword</code>
- * configuration attributes are detected, the request will be deemed to be
- * requesting a secure channel. This is a reasonable approach, as when in
- * doubt, the decision manager assumes the most secure outcome is desired. Of
- * course, you <b>should</b> indicate one configuration attribute or the other
- * (not both).
+ * <p>
+ * Iterates through each configured {@link ChannelProcessor}. If a
+ * <code>ChannelProcessor</code> has any issue with the security of the
+ * request, it should cause a redirect, exception or whatever other action is
+ * appropriate for the <code>ChannelProcessor</code> implementation.
  * </p>
  * 
  * <P>
- * The default <code>secureKeyword</code> and <code>insecureKeyword</code> is
- * <code>REQUIRES_SECURE_CHANNEL</code> and
- * <code>REQUIRES_INSECURE_CHANNEL</code> respectively.
+ * 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>
  *
  * @author Ben Alex
  * @version $Id$
  */
-public class ChannelDecisionManagerImpl implements InitializingBean,
-    ChannelDecisionManager {
+public class ChannelDecisionManagerImpl implements ChannelDecisionManager,
+    InitializingBean {
     //~ Instance fields ========================================================
 
-    private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
-    private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
+    private List channelProcessors;
 
     //~ Methods ================================================================
 
-    public void setInsecureKeyword(String insecureKeyword) {
-        this.insecureKeyword = insecureKeyword;
-    }
+    public void setChannelProcessors(List newList) {
+        checkIfValidList(newList);
 
-    public String getInsecureKeyword() {
-        return insecureKeyword;
-    }
+        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");
+            }
+        }
 
-    public void setSecureKeyword(String secureKeyword) {
-        this.secureKeyword = secureKeyword;
+        this.channelProcessors = newList;
     }
 
-    public String getSecureKeyword() {
-        return secureKeyword;
+    public List getChannelProcessors() {
+        return this.channelProcessors;
     }
 
     public void afterPropertiesSet() throws Exception {
-        if ((secureKeyword == null) || "".equals(secureKeyword)) {
-            throw new IllegalArgumentException("secureKeyword required");
-        }
-
-        if ((insecureKeyword == null) || "".equals(insecureKeyword)) {
-            throw new IllegalArgumentException("insecureKeyword required");
-        }
+        checkIfValidList(this.channelProcessors);
     }
 
     public void decide(FilterInvocation invocation,
-        ConfigAttributeDefinition config) throws SecureChannelRequiredException {
-        if ((invocation == null) || (config == null)) {
-            throw new IllegalArgumentException("Nulls cannot be provided");
+        ConfigAttributeDefinition config) throws IOException, ServletException {
+        Iterator iter = this.channelProcessors.iterator();
+
+        while (iter.hasNext()) {
+            ChannelProcessor processor = (ChannelProcessor) iter.next();
+
+            processor.decide(invocation, config);
+
+            if (invocation.getResponse().isCommitted()) {
+                break;
+            }
         }
+    }
 
-        Iterator iter = config.getConfigAttributes();
+    public boolean supports(ConfigAttribute attribute) {
+        Iterator iter = this.channelProcessors.iterator();
 
         while (iter.hasNext()) {
-            ConfigAttribute attribute = (ConfigAttribute) iter.next();
+            ChannelProcessor processor = (ChannelProcessor) iter.next();
 
-            if (attribute.equals(secureKeyword)) {
-                if (!invocation.getHttpRequest().isSecure()) {
-                    throw new SecureChannelRequiredException(
-                        "Request is not being made over a secure channel");
-                }
+            if (processor.supports(attribute)) {
+                return true;
             }
+        }
 
-            if (attribute.equals(insecureKeyword)) {
-                if (invocation.getHttpRequest().isSecure()) {
-                    throw new InsecureChannelRequiredException(
-                        "Request is being made over a secure channel when an insecure channel is required");
-                }
-            }
+        return false;
+    }
+
+    private void checkIfValidList(List listToCheck) {
+        if ((listToCheck == null) || (listToCheck.size() == 0)) {
+            throw new IllegalArgumentException(
+                "A list of ChannelProcessors is required");
         }
     }
 }

+ 7 - 8
core/src/main/java/org/acegisecurity/securechannel/ChannelEntryPoint.java

@@ -23,11 +23,13 @@ import javax.servlet.ServletResponse;
 
 
 /**
- * Used by {@link ChannelProcessingFilter} to launch a web channel.
+ * May be used by a {@link ChannelProcessor} to launch a web channel.
  * 
  * <P>
- * Depending on the implementation, a secure or insecure channel will be
- * launched.
+ * <code>ChannelProcessor</code>s can elect to launch a new web channel
+ * directly, or they can delegate to another class. The
+ * <code>ChannelEntryPoint</code> is a pluggable interface to assist
+ * <code>ChannelProcessor</code>s in performing this delegation.
  * </p>
  *
  * @author Ben Alex
@@ -42,13 +44,10 @@ public interface ChannelEntryPoint {
      * <P>
      * Implementations should modify the headers on the
      * <code>ServletResponse</code> as necessary to commence the user agent
-     * using the implementation's supported channel type (ie secure or
-     * insecure).
+     * using the implementation's supported channel type.
      * </p>
      *
-     * @param request that resulted in a
-     *        <code>SecureChannelRequiredException</code> or
-     *        <code>InsecureChannelRequiredException</code>
+     * @param request that a <code>ChannelProcessor</code> has rejected
      * @param response so that the user agent can begin using a new channel
      */
     public void commence(ServletRequest request, ServletResponse response)

+ 45 - 44
core/src/main/java/org/acegisecurity/securechannel/ChannelProcessingFilter.java

@@ -15,6 +15,7 @@
 
 package net.sf.acegisecurity.securechannel;
 
+import net.sf.acegisecurity.ConfigAttribute;
 import net.sf.acegisecurity.ConfigAttributeDefinition;
 import net.sf.acegisecurity.intercept.web.FilterInvocation;
 import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
@@ -26,6 +27,10 @@ import org.springframework.beans.factory.InitializingBean;
 
 import java.io.IOException;
 
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -46,6 +51,12 @@ import javax.servlet.http.HttpServletResponse;
  * </p>
  * 
  * <P>
+ * Delegates the actual channel security decisions and necessary actions to the
+ * configured {@link ChannelDecisionManager}. If a response is committed by
+ * the <code>ChannelDecisionManager</code>, the filter chain will not proceed.
+ * </p>
+ * 
+ * <P>
  * <B>Do not use this class directly.</B> Instead configure
  * <code>web.xml</code> to use the {@link
  * net.sf.acegisecurity.util.FilterToBeanProxy}.
@@ -62,8 +73,6 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
     //~ Instance fields ========================================================
 
     private ChannelDecisionManager channelDecisionManager;
-    private ChannelEntryPoint insecureChannelEntryPoint;
-    private ChannelEntryPoint secureChannelEntryPoint;
     private FilterInvocationDefinitionSource filterInvocationDefinitionSource;
 
     //~ Methods ================================================================
@@ -86,23 +95,6 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
         return filterInvocationDefinitionSource;
     }
 
-    public void setInsecureChannelEntryPoint(
-        ChannelEntryPoint insecureChannelEntryPoint) {
-        this.insecureChannelEntryPoint = insecureChannelEntryPoint;
-    }
-
-    public ChannelEntryPoint getInsecureChannelEntryPoint() {
-        return insecureChannelEntryPoint;
-    }
-
-    public void setSecureChannelEntryPoint(ChannelEntryPoint channelEntryPoint) {
-        this.secureChannelEntryPoint = channelEntryPoint;
-    }
-
-    public ChannelEntryPoint getSecureChannelEntryPoint() {
-        return secureChannelEntryPoint;
-    }
-
     public void afterPropertiesSet() throws Exception {
         if (filterInvocationDefinitionSource == null) {
             throw new IllegalArgumentException(
@@ -114,14 +106,41 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
                 "channelDecisionManager must be specified");
         }
 
-        if (secureChannelEntryPoint == null) {
-            throw new IllegalArgumentException(
-                "secureChannelEntryPoint must be specified");
+        Iterator iter = this.filterInvocationDefinitionSource
+            .getConfigAttributeDefinitions();
+
+        if (iter == null) {
+            if (logger.isWarnEnabled()) {
+                logger.warn(
+                    "Could not validate configuration attributes as the FilterInvocationDefinitionSource did not return a ConfigAttributeDefinition Iterator");
+            }
+
+            return;
+        }
+
+        Set set = new HashSet();
+
+        while (iter.hasNext()) {
+            ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter
+                .next();
+            Iterator attributes = def.getConfigAttributes();
+
+            while (attributes.hasNext()) {
+                ConfigAttribute attr = (ConfigAttribute) attributes.next();
+
+                if (!this.channelDecisionManager.supports(attr)) {
+                    set.add(attr);
+                }
+            }
         }
 
-        if (insecureChannelEntryPoint == null) {
+        if (set.size() == 0) {
+            if (logger.isInfoEnabled()) {
+                logger.info("Validated configuration attributes");
+            }
+        } else {
             throw new IllegalArgumentException(
-                "insecureChannelEntryPoint must be specified");
+                "Unsupported configuration attributes: " + set.toString());
         }
     }
 
@@ -147,27 +166,9 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
                     + "; ConfigAttributes: " + attr.toString());
             }
 
-            try {
-                channelDecisionManager.decide(fi, attr);
-            } catch (SecureChannelRequiredException secureException) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Channel insufficient security ("
-                        + secureException.getMessage()
-                        + "); delegating to secureChannelEntryPoint");
-                }
-
-                secureChannelEntryPoint.commence(request, response);
-
-                return;
-            } catch (InsecureChannelRequiredException insecureException) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Channel too much security ("
-                        + insecureException.getMessage()
-                        + "); delegating to insecureChannelEntryPoint");
-                }
-
-                insecureChannelEntryPoint.commence(request, response);
+            channelDecisionManager.decide(fi, attr);
 
+            if (fi.getResponse().isCommitted()) {
                 return;
             }
         }

+ 72 - 0
core/src/main/java/org/acegisecurity/securechannel/ChannelProcessor.java

@@ -0,0 +1,72 @@
+/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
+
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+
+/**
+ * Decides whether a web channel meets a specific security condition.
+ * 
+ * <P>
+ * <code>ChannelProcessor</code> implementations are iterated by the {@link
+ * ChannelDecisionManagerImpl}.
+ * </p>
+ * 
+ * <P>
+ * If an implementation has an issue with the channel security, they should
+ * take action themselves. The callers of the implementation do not take any
+ * action.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface ChannelProcessor {
+    //~ Methods ================================================================
+
+    /**
+     * Decided whether the presented {@link FilterInvocation} provides the
+     * appropriate level of channel security based on the requested {@link
+     * ConfigAttributeDefinition}.
+     */
+    public void decide(FilterInvocation invocation,
+        ConfigAttributeDefinition config) throws IOException, ServletException;
+
+    /**
+     * Indicates whether this <code>ChannelProcessor</code> is able to process
+     * the passed <code>ConfigAttribute</code>.
+     * 
+     * <p>
+     * This allows the <code>ChannelProcessingFilter</code> to check every
+     * configuration attribute can be consumed by the configured
+     * <code>ChannelDecisionManager</code>.
+     * </p>
+     *
+     * @param attribute a configuration attribute that has been configured
+     *        against the <code>ChannelProcessingFilter</code>
+     *
+     * @return true if this <code>ChannelProcessor</code> can support the
+     *         passed configuration attribute
+     */
+    public boolean supports(ConfigAttribute attribute);
+}

+ 117 - 0
core/src/main/java/org/acegisecurity/securechannel/InsecureChannelProcessor.java

@@ -0,0 +1,117 @@
+/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
+
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import javax.servlet.ServletException;
+
+
+/**
+ * <p>
+ * Ensures channel security is inactive by review of
+ * <code>HttpServletRequest.isSecure()</code> responses.
+ * </p>
+ * 
+ * <P>
+ * The class responds to one case-sensitive keyword, {@link
+ * #getInsecureKeyword}. If this keyword is detected,
+ * <code>HttpServletRequest.isSecure()</code> is used to determine the channel
+ * security offered. If channel security is present, the configured
+ * <code>ChannelEntryPoint</code> is called. By default the entry point is
+ * {@link RetryWithHttpEntryPoint}.
+ * </p>
+ * 
+ * <P>
+ * The default <code>insecureKeyword</code> is
+ * <code>REQUIRES_INSECURE_CHANNEL</code>.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class InsecureChannelProcessor implements InitializingBean,
+    ChannelProcessor {
+    //~ Instance fields ========================================================
+
+    private ChannelEntryPoint entryPoint = new RetryWithHttpEntryPoint();
+    private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
+
+    //~ Methods ================================================================
+
+    public void setEntryPoint(ChannelEntryPoint entryPoint) {
+        this.entryPoint = entryPoint;
+    }
+
+    public ChannelEntryPoint getEntryPoint() {
+        return entryPoint;
+    }
+
+    public void setInsecureKeyword(String secureKeyword) {
+        this.insecureKeyword = secureKeyword;
+    }
+
+    public String getInsecureKeyword() {
+        return insecureKeyword;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if ((insecureKeyword == null) || "".equals(insecureKeyword)) {
+            throw new IllegalArgumentException("insecureKeyword required");
+        }
+
+        if (entryPoint == null) {
+            throw new IllegalArgumentException("entryPoint required");
+        }
+    }
+
+    public void decide(FilterInvocation invocation,
+        ConfigAttributeDefinition config) throws IOException, ServletException {
+        if ((invocation == null) || (config == null)) {
+            throw new IllegalArgumentException("Nulls cannot be provided");
+        }
+
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+            if (supports(attribute)) {
+                if (invocation.getHttpRequest().isSecure()) {
+                    entryPoint.commence(invocation.getRequest(),
+                        invocation.getResponse());
+                }
+            }
+        }
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute != null) && (attribute.getAttribute() != null)
+            && attribute.getAttribute().equals(getInsecureKeyword())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 0 - 50
core/src/main/java/org/acegisecurity/securechannel/InsecureChannelRequiredException.java

@@ -1,50 +0,0 @@
-/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
-
-import net.sf.acegisecurity.AccessDeniedException;
-
-
-/**
- * Thrown if a secure web channel is detected, but is not required.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class InsecureChannelRequiredException extends AccessDeniedException {
-    //~ Constructors ===========================================================
-
-    /**
-     * Constructs an <code>InsecureChannelRequiredException</code> with the
-     * specified message.
-     *
-     * @param msg the detail message.
-     */
-    public InsecureChannelRequiredException(String msg) {
-        super(msg);
-    }
-
-    /**
-     * Constructs an <code>InsecureChannelRequiredException</code> with the
-     * specified message and root cause.
-     *
-     * @param msg the detail message.
-     * @param t root cause
-     */
-    public InsecureChannelRequiredException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}

+ 116 - 0
core/src/main/java/org/acegisecurity/securechannel/SecureChannelProcessor.java

@@ -0,0 +1,116 @@
+/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
+
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import javax.servlet.ServletException;
+
+
+/**
+ * <p>
+ * Ensures channel security is active by review of
+ * <code>HttpServletRequest.isSecure()</code> responses.
+ * </p>
+ * 
+ * <P>
+ * The class responds to one case-sensitive keyword, {@link #getSecureKeyword}.
+ * If this keyword is detected, <code>HttpServletRequest.isSecure()</code> is
+ * used to determine the channel security offered. If channel security is not
+ * present, the configured <code>ChannelEntryPoint</code> is called. By
+ * default the entry point is {@link RetryWithHttpsEntryPoint}.
+ * </p>
+ * 
+ * <P>
+ * The default <code>secureKeyword</code> is
+ * <code>REQUIRES_SECURE_CHANNEL</code>.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SecureChannelProcessor implements InitializingBean,
+    ChannelProcessor {
+    //~ Instance fields ========================================================
+
+    private ChannelEntryPoint entryPoint = new RetryWithHttpsEntryPoint();
+    private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
+
+    //~ Methods ================================================================
+
+    public void setEntryPoint(ChannelEntryPoint entryPoint) {
+        this.entryPoint = entryPoint;
+    }
+
+    public ChannelEntryPoint getEntryPoint() {
+        return entryPoint;
+    }
+
+    public void setSecureKeyword(String secureKeyword) {
+        this.secureKeyword = secureKeyword;
+    }
+
+    public String getSecureKeyword() {
+        return secureKeyword;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if ((secureKeyword == null) || "".equals(secureKeyword)) {
+            throw new IllegalArgumentException("secureKeyword required");
+        }
+
+        if (entryPoint == null) {
+            throw new IllegalArgumentException("entryPoint required");
+        }
+    }
+
+    public void decide(FilterInvocation invocation,
+        ConfigAttributeDefinition config) throws IOException, ServletException {
+        if ((invocation == null) || (config == null)) {
+            throw new IllegalArgumentException("Nulls cannot be provided");
+        }
+
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+            if (supports(attribute)) {
+                if (!invocation.getHttpRequest().isSecure()) {
+                    entryPoint.commence(invocation.getRequest(),
+                        invocation.getResponse());
+                }
+            }
+        }
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute != null) && (attribute.getAttribute() != null)
+            && attribute.getAttribute().equals(getSecureKeyword())) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 0 - 50
core/src/main/java/org/acegisecurity/securechannel/SecureChannelRequiredException.java

@@ -1,50 +0,0 @@
-/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
-
-import net.sf.acegisecurity.AccessDeniedException;
-
-
-/**
- * Thrown if a secure web channel is required, but is not present.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class SecureChannelRequiredException extends AccessDeniedException {
-    //~ Constructors ===========================================================
-
-    /**
-     * Constructs a <code>SecureChannelRequiredException</code> with the
-     * specified message.
-     *
-     * @param msg the detail message.
-     */
-    public SecureChannelRequiredException(String msg) {
-        super(msg);
-    }
-
-    /**
-     * Constructs a <code>SecureChannelRequiredException</code> with the
-     * specified message and root cause.
-     *
-     * @param msg the detail message.
-     * @param t root cause
-     */
-    public SecureChannelRequiredException(String msg, Throwable t) {
-        super(msg, t);
-    }
-}

+ 5 - 1
core/src/test/java/org/acegisecurity/MockHttpServletResponse.java

@@ -56,7 +56,11 @@ public class MockHttpServletResponse implements HttpServletResponse {
     }
 
     public boolean isCommitted() {
-        throw new UnsupportedOperationException("mock method not implemented");
+        if (redirect == null) {
+            return false;
+        } else {
+            return true;
+        }
     }
 
     public void setContentLength(int arg0) {

+ 129 - 86
core/src/test/java/org/acegisecurity/securechannel/ChannelDecisionManagerImplTests.java

@@ -17,6 +17,7 @@ package net.sf.acegisecurity.securechannel;
 
 import junit.framework.TestCase;
 
+import net.sf.acegisecurity.ConfigAttribute;
 import net.sf.acegisecurity.ConfigAttributeDefinition;
 import net.sf.acegisecurity.MockFilterChain;
 import net.sf.acegisecurity.MockHttpServletRequest;
@@ -24,6 +25,14 @@ import net.sf.acegisecurity.MockHttpServletResponse;
 import net.sf.acegisecurity.SecurityConfig;
 import net.sf.acegisecurity.intercept.web.FilterInvocation;
 
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.servlet.ServletException;
+
 
 /**
  * Tests {@link ChannelDecisionManagerImpl}.
@@ -42,141 +51,175 @@ public class ChannelDecisionManagerImplTests extends TestCase {
         junit.textui.TestRunner.run(ChannelDecisionManagerImplTests.class);
     }
 
-    public void testDetectsInvalidInsecureKeyword() throws Exception {
+    public void testCannotSetEmptyChannelProcessorsList()
+        throws Exception {
         ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
-        cdm.setInsecureKeyword("");
 
         try {
-            cdm.afterPropertiesSet();
+            cdm.setChannelProcessors(new Vector());
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
-            assertEquals("insecureKeyword required", expected.getMessage());
+            assertEquals("A list of ChannelProcessors is required",
+                expected.getMessage());
         }
+    }
 
-        cdm.setInsecureKeyword(null);
+    public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList()
+        throws Exception {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        List list = new Vector();
+        list.add("THIS IS NOT A CHANNELPROCESSOR");
 
         try {
-            cdm.afterPropertiesSet();
+            cdm.setChannelProcessors(list);
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
-            assertEquals("insecureKeyword required", expected.getMessage());
+            assertTrue(true);
         }
     }
 
-    public void testDetectsInvalidSecureKeyword() throws Exception {
+    public void testCannotSetNullChannelProcessorsList()
+        throws Exception {
         ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
-        cdm.setSecureKeyword("");
 
         try {
-            cdm.afterPropertiesSet();
+            cdm.setChannelProcessors(null);
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
-            assertEquals("secureKeyword required", expected.getMessage());
+            assertEquals("A list of ChannelProcessors is required",
+                expected.getMessage());
         }
+    }
 
-        cdm.setSecureKeyword(null);
+    public void testDecideIsOperational() throws Exception {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
+        MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
+        List list = new Vector();
+        list.add(cpXyz);
+        list.add(cpAbc);
+        cdm.setChannelProcessors(list);
+        cdm.afterPropertiesSet();
 
-        try {
-            cdm.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertEquals("secureKeyword required", expected.getMessage());
-        }
+        MockHttpServletRequest request = new MockHttpServletRequest("not used");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("xyz"));
+
+        cdm.decide(fi, cad);
+        assertTrue(fi.getResponse().isCommitted());
     }
 
-    public void testDetectsNullsPassedToMainMethod() {
+    public void testDecideIteratesAllProcessorsIfNoneCommitAResponse()
+        throws Exception {
         ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
+        MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
+        List list = new Vector();
+        list.add(cpXyz);
+        list.add(cpAbc);
+        cdm.setChannelProcessors(list);
+        cdm.afterPropertiesSet();
 
-        try {
-            cdm.decide(null, new ConfigAttributeDefinition());
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertEquals("Nulls cannot be provided", expected.getMessage());
-        }
+        MockHttpServletRequest request = new MockHttpServletRequest("not used");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
 
-        try {
-            cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"),
-                    new MockHttpServletResponse(), new MockFilterChain()), null);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertEquals("Nulls cannot be provided", expected.getMessage());
-        }
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig(
+                "SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT"));
+
+        cdm.decide(fi, cad);
+        assertFalse(fi.getResponse().isCommitted());
     }
 
-    public void testDetectsWhenInsecureChannelNeededAndInsecureSchemeUsed() {
-        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig(
-                "SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
-        attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+    public void testDelegatesSupports() throws Exception {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
+        MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
+        List list = new Vector();
+        list.add(cpXyz);
+        list.add(cpAbc);
+        cdm.setChannelProcessors(list);
+        cdm.afterPropertiesSet();
 
-        MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
-        request.setScheme("http");
+        assertTrue(cdm.supports(new SecurityConfig("xyz")));
+        assertTrue(cdm.supports(new SecurityConfig("abc")));
+        assertFalse(cdm.supports(new SecurityConfig("UNSUPPORTED")));
+    }
 
+    public void testGettersSetters() {
         ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
-        cdm.decide(new FilterInvocation(request, new MockHttpServletResponse(),
-                new MockFilterChain()), attr);
-        assertTrue(true);
-    }
+        assertNull(cdm.getChannelProcessors());
 
-    public void testDetectsWhenInsecureChannelNeededAndSecureSchemeUsed() {
-        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig(
-                "SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
-        attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+        MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
+        MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
+        List list = new Vector();
+        list.add(cpXyz);
+        list.add(cpAbc);
+        cdm.setChannelProcessors(list);
 
-        MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
-        request.setScheme("https");
+        assertEquals(list, cdm.getChannelProcessors());
+    }
 
+    public void testStartupFailsWithEmptyChannelProcessorsList()
+        throws Exception {
         ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
 
         try {
-            cdm.decide(new FilterInvocation(request,
-                    new MockHttpServletResponse(), new MockFilterChain()), attr);
-        } catch (InsecureChannelRequiredException expected) {
-            assertTrue(true);
+            cdm.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("A list of ChannelProcessors is required",
+                expected.getMessage());
         }
     }
 
-    public void testDetectsWhenSecureChannelNeeded() {
-        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig(
-                "SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
-        attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+    //~ Inner Classes ==========================================================
 
-        MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
-        request.setScheme("http");
+    private class MockChannelProcessor implements ChannelProcessor {
+        private String configAttribute;
+        private boolean failIfCalled;
 
-        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        public MockChannelProcessor(String configAttribute, boolean failIfCalled) {
+            this.configAttribute = configAttribute;
+            this.failIfCalled = failIfCalled;
+        }
 
-        try {
-            cdm.decide(new FilterInvocation(request,
-                    new MockHttpServletResponse(), new MockFilterChain()), attr);
-        } catch (SecureChannelRequiredException expected) {
-            assertTrue(true);
+        private MockChannelProcessor() {
+            super();
         }
-    }
 
-    public void testGetterSetters() throws Exception {
-        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
-        cdm.afterPropertiesSet();
-        assertEquals("REQUIRES_INSECURE_CHANNEL", cdm.getInsecureKeyword());
-        assertEquals("REQUIRES_SECURE_CHANNEL", cdm.getSecureKeyword());
+        public void decide(FilterInvocation invocation,
+            ConfigAttributeDefinition config)
+            throws IOException, ServletException {
+            Iterator iter = config.getConfigAttributes();
 
-        cdm.setInsecureKeyword("MY_INSECURE");
-        cdm.setSecureKeyword("MY_SECURE");
+            if (failIfCalled) {
+                fail("Should not have called this channel processor");
+            }
 
-        assertEquals("MY_INSECURE", cdm.getInsecureKeyword());
-        assertEquals("MY_SECURE", cdm.getSecureKeyword());
-    }
+            while (iter.hasNext()) {
+                ConfigAttribute attr = (ConfigAttribute) iter.next();
 
-    public void testIgnoresOtherConfigAttributes() {
-        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig("XYZ"));
+                if (attr.equals(configAttribute)) {
+                    invocation.getHttpResponse().sendRedirect("/redirected");
 
-        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
-        cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"),
-                new MockHttpServletResponse(), new MockFilterChain()), attr);
-        assertTrue(true);
+                    return;
+                }
+            }
+        }
+
+        public boolean supports(ConfigAttribute attribute) {
+            if (attribute.getAttribute().equals(configAttribute)) {
+                return true;
+            } else {
+                return false;
+            }
+        }
     }
 }
-;

+ 139 - 131
core/src/test/java/org/acegisecurity/securechannel/ChannelProcessingFilterTests.java

@@ -17,18 +17,19 @@ package net.sf.acegisecurity.securechannel;
 
 import junit.framework.TestCase;
 
+import net.sf.acegisecurity.ConfigAttribute;
 import net.sf.acegisecurity.ConfigAttributeDefinition;
-import net.sf.acegisecurity.MockFilterConfig;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletResponse;
 import net.sf.acegisecurity.SecurityConfig;
 import net.sf.acegisecurity.intercept.web.FilterInvocation;
 import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
-import net.sf.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
 
 import java.io.IOException;
 
 import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
 
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
@@ -53,61 +54,16 @@ public class ChannelProcessingFilterTests extends TestCase {
         junit.textui.TestRunner.run(ChannelProcessingFilterTests.class);
     }
 
-    public void testCallsInsecureEntryPointWhenTooMuchChannelSecurity()
+    public void testDetectsMissingChannelDecisionManager()
         throws Exception {
-        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
-
-        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
-                attr);
-
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new MockEntryPoint(true));
-        filter.setSecureChannelEntryPoint(new MockEntryPoint(false));
-        filter.setFilterInvocationDefinitionSource(fids);
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
-
-        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
-        request.setServletPath("/path");
-        request.setScheme("https");
 
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain(false);
-
-        filter.doFilter(request, response, chain);
-        assertTrue(true);
-    }
-
-    public void testCallsSecureEntryPointWhenTooLittleChannelSecurity()
-        throws Exception {
         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+        attr.addConfigAttribute(new SecurityConfig("MOCK"));
 
         MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
-                attr);
-
-        ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new MockEntryPoint(false));
-        filter.setSecureChannelEntryPoint(new MockEntryPoint(true));
+                attr, true);
         filter.setFilterInvocationDefinitionSource(fids);
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
-
-        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
-        request.setServletPath("/path");
-        request.setScheme("http");
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain(false);
-
-        filter.doFilter(request, response, chain);
-        assertTrue(true);
-    }
-
-    public void testDetectsMissingChannelDecisionManager()
-        throws Exception {
-        ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
-        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
 
         try {
             filter.afterPropertiesSet();
@@ -118,12 +74,11 @@ public class ChannelProcessingFilterTests extends TestCase {
         }
     }
 
-    public void testDetectsMissingFilterInvocationDefinitionMap()
+    public void testDetectsMissingFilterInvocationDefinitionSource()
         throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(false,
+                "MOCK"));
 
         try {
             filter.afterPropertiesSet();
@@ -134,81 +89,86 @@ public class ChannelProcessingFilterTests extends TestCase {
         }
     }
 
-    public void testDetectsMissingInsecureChannelEntryPoint()
-        throws Exception {
+    public void testDetectsSupportedConfigAttribute() throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
-        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(false,
+                "SUPPORTS_MOCK_ONLY"));
 
-        try {
-            filter.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertEquals("insecureChannelEntryPoint must be specified",
-                expected.getMessage());
-        }
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("SUPPORTS_MOCK_ONLY"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr, true);
+
+        filter.setFilterInvocationDefinitionSource(fids);
+
+        filter.afterPropertiesSet();
+        assertTrue(true);
     }
 
-    public void testDetectsMissingSecureChannelEntryPoint()
+    public void testDetectsUnsupportedConfigAttribute()
         throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
-        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(false,
+                "SUPPORTS_MOCK_ONLY"));
+
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("SUPPORTS_MOCK_ONLY"));
+        attr.addConfigAttribute(new SecurityConfig("INVALID_ATTRIBUTE"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr, true);
+
+        filter.setFilterInvocationDefinitionSource(fids);
 
         try {
             filter.afterPropertiesSet();
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
-            assertEquals("secureChannelEntryPoint must be specified",
-                expected.getMessage());
+            assertTrue(expected.getMessage().startsWith("Unsupported configuration attributes:"));
         }
     }
 
-    public void testDoFilterWithNonHttpServletRequestDetected()
+    public void testDoFilterWhenManagerDoesCommitResponse()
         throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(true,
+                "SOME_ATTRIBUTE"));
 
-        try {
-            filter.doFilter(null, new MockHttpServletResponse(),
-                new MockFilterChain());
-            fail("Should have thrown ServletException");
-        } catch (ServletException expected) {
-            assertEquals("HttpServletRequest required", expected.getMessage());
-        }
-    }
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("SOME_ATTRIBUTE"));
 
-    public void testDoFilterWithNonHttpServletResponseDetected()
-        throws Exception {
-        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr, true);
 
-        try {
-            filter.doFilter(new MockHttpServletRequest(null, null), null,
-                new MockFilterChain());
-            fail("Should have thrown ServletException");
-        } catch (ServletException expected) {
-            assertEquals("HttpServletResponse required", expected.getMessage());
-        }
+        filter.setFilterInvocationDefinitionSource(fids);
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
+        request.setServletPath("/path");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(false);
+
+        filter.doFilter(request, response, chain);
+        assertTrue(true);
     }
 
-    public void testDoesNotInterruptRequestsWithCorrectChannelSecurity()
+    public void testDoFilterWhenManagerDoesNotCommitResponse()
         throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(false,
+                "SOME_ATTRIBUTE"));
+
         ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
-        attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+        attr.addConfigAttribute(new SecurityConfig("SOME_ATTRIBUTE"));
 
         MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
-                attr);
+                attr, true);
 
-        ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
         filter.setFilterInvocationDefinitionSource(fids);
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
 
         MockHttpServletRequest request = new MockHttpServletRequest("info=now");
         request.setServletPath("/path");
-        request.setScheme("https");
 
         MockHttpServletResponse response = new MockHttpServletResponse();
         MockFilterChain chain = new MockFilterChain(true);
@@ -217,15 +177,23 @@ public class ChannelProcessingFilterTests extends TestCase {
         assertTrue(true);
     }
 
-    public void testDoesNotInterruptRequestsWithNoConfigAttribute()
+    public void testDoFilterWhenNullConfigAttributeReturned()
         throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
-        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(false,
+                "NOT_USED"));
+
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("NOT_USED"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr, true);
+
+        filter.setFilterInvocationDefinitionSource(fids);
 
         MockHttpServletRequest request = new MockHttpServletRequest("info=now");
+        request.setServletPath("/PATH_NOT_MATCHING_CONFIG_ATTRIBUTE");
+
         MockHttpServletResponse response = new MockHttpServletResponse();
         MockFilterChain chain = new MockFilterChain(true);
 
@@ -233,50 +201,81 @@ public class ChannelProcessingFilterTests extends TestCase {
         assertTrue(true);
     }
 
-    public void testGetterSetters() {
+    public void testDoFilterWithNonHttpServletRequestDetected()
+        throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
-        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
 
-        assertTrue(filter.getInsecureChannelEntryPoint() != null);
-        assertTrue(filter.getSecureChannelEntryPoint() != null);
-        assertTrue(filter.getFilterInvocationDefinitionSource() != null);
-        assertTrue(filter.getChannelDecisionManager() != null);
+        try {
+            filter.doFilter(null, new MockHttpServletResponse(),
+                new MockFilterChain());
+            fail("Should have thrown ServletException");
+        } catch (ServletException expected) {
+            assertEquals("HttpServletRequest required", expected.getMessage());
+        }
     }
 
-    public void testLifecycle() throws Exception {
+    public void testDoFilterWithNonHttpServletResponseDetected()
+        throws Exception {
         ChannelProcessingFilter filter = new ChannelProcessingFilter();
-        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
-        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
-        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
-        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
-        filter.afterPropertiesSet();
 
-        filter.init(new MockFilterConfig());
+        try {
+            filter.doFilter(new MockHttpServletRequest(null, null), null,
+                new MockFilterChain());
+            fail("Should have thrown ServletException");
+        } catch (ServletException expected) {
+            assertEquals("HttpServletResponse required", expected.getMessage());
+        }
+    }
+
+    public void testGetterSetters() throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setChannelDecisionManager(new MockChannelDecisionManager(false,
+                "MOCK"));
+        assertTrue(filter.getChannelDecisionManager() != null);
+
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("MOCK"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr, false);
+
+        filter.setFilterInvocationDefinitionSource(fids);
+        assertTrue(filter.getFilterInvocationDefinitionSource() != null);
+
+        filter.init(null);
+        filter.afterPropertiesSet();
         filter.destroy();
     }
 
     //~ Inner Classes ==========================================================
 
-    private class MockEntryPoint implements ChannelEntryPoint {
-        private boolean expectToBeCalled;
+    private class MockChannelDecisionManager implements ChannelDecisionManager {
+        private String supportAttribute;
+        private boolean commitAResponse;
 
-        public MockEntryPoint(boolean expectToBeCalled) {
-            this.expectToBeCalled = expectToBeCalled;
+        public MockChannelDecisionManager(boolean commitAResponse,
+            String supportAttribute) {
+            this.commitAResponse = commitAResponse;
+            this.supportAttribute = supportAttribute;
         }
 
-        private MockEntryPoint() {
+        private MockChannelDecisionManager() {
             super();
         }
 
-        public void commence(ServletRequest request, ServletResponse response)
+        public void decide(FilterInvocation invocation,
+            ConfigAttributeDefinition config)
             throws IOException, ServletException {
-            if (expectToBeCalled) {
-                assertTrue(true);
+            if (commitAResponse) {
+                invocation.getHttpResponse().sendRedirect("/redirected");
+            }
+        }
+
+        public boolean supports(ConfigAttribute attribute) {
+            if (attribute.getAttribute().equals(supportAttribute)) {
+                return true;
             } else {
-                fail("Did not expect this ChannelEntryPoint to be called");
+                return false;
             }
         }
     }
@@ -306,11 +305,13 @@ public class ChannelProcessingFilterTests extends TestCase {
         implements FilterInvocationDefinitionSource {
         private ConfigAttributeDefinition toReturn;
         private String servletPath;
+        private boolean provideIterator;
 
         public MockFilterInvocationDefinitionMap(String servletPath,
-            ConfigAttributeDefinition toReturn) {
+            ConfigAttributeDefinition toReturn, boolean provideIterator) {
             this.servletPath = servletPath;
             this.toReturn = toReturn;
+            this.provideIterator = provideIterator;
         }
 
         private MockFilterInvocationDefinitionMap() {
@@ -329,7 +330,14 @@ public class ChannelProcessingFilterTests extends TestCase {
         }
 
         public Iterator getConfigAttributeDefinitions() {
-            return null;
+            if (!provideIterator) {
+                return null;
+            }
+
+            List list = new Vector();
+            list.add(toReturn);
+
+            return list.iterator();
         }
 
         public boolean supports(Class clazz) {

+ 153 - 0
core/src/test/java/org/acegisecurity/securechannel/InsecureChannelProcessorTests.java

@@ -0,0 +1,153 @@
+/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+
+/**
+ * Tests {@link InsecureChannelProcessor}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class InsecureChannelProcessorTests extends TestCase {
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(InsecureChannelProcessorTests.class);
+    }
+
+    public void testDecideDetectsAcceptableChannel() throws Exception {
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
+        cad.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=true");
+        request.setServerName("localhost");
+        request.setContextPath("/bigapp");
+        request.setServletPath("/servlet");
+        request.setScheme("http");
+        request.setServerPort(8080);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        processor.decide(fi, cad);
+
+        assertFalse(fi.getResponse().isCommitted());
+    }
+
+    public void testDecideDetectsUnacceptableChannel()
+        throws Exception {
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
+        cad.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=true");
+        request.setServerName("localhost");
+        request.setContextPath("/bigapp");
+        request.setServletPath("/servlet");
+        request.setScheme("https");
+        request.setServerPort(8443);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        processor.decide(fi, cad);
+
+        assertTrue(fi.getResponse().isCommitted());
+    }
+
+    public void testDecideRejectsNulls() throws Exception {
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        processor.afterPropertiesSet();
+
+        try {
+            processor.decide(null, null);
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGettersSetters() {
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        assertEquals("REQUIRES_INSECURE_CHANNEL", processor.getInsecureKeyword());
+        processor.setInsecureKeyword("X");
+        assertEquals("X", processor.getInsecureKeyword());
+
+        assertTrue(processor.getEntryPoint() != null);
+        processor.setEntryPoint(null);
+        assertTrue(processor.getEntryPoint() == null);
+    }
+
+    public void testMissingEntryPoint() throws Exception {
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        processor.setEntryPoint(null);
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("entryPoint required", expected.getMessage());
+        }
+    }
+
+    public void testMissingSecureChannelKeyword() throws Exception {
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        processor.setInsecureKeyword(null);
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("insecureKeyword required", expected.getMessage());
+        }
+
+        processor.setInsecureKeyword("");
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("insecureKeyword required", expected.getMessage());
+        }
+    }
+
+    public void testSupports() {
+        InsecureChannelProcessor processor = new InsecureChannelProcessor();
+        assertTrue(processor.supports(
+                new SecurityConfig("REQUIRES_INSECURE_CHANNEL")));
+        assertFalse(processor.supports(null));
+        assertFalse(processor.supports(new SecurityConfig("NOT_SUPPORTED")));
+    }
+}

+ 153 - 0
core/src/test/java/org/acegisecurity/securechannel/SecureChannelProcessorTests.java

@@ -0,0 +1,153 @@
+/* Copyright 2004 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 net.sf.acegisecurity.securechannel;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+
+/**
+ * Tests {@link SecureChannelProcessor}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SecureChannelProcessorTests extends TestCase {
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(SecureChannelProcessorTests.class);
+    }
+
+    public void testDecideDetectsAcceptableChannel() throws Exception {
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
+        cad.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=true");
+        request.setServerName("localhost");
+        request.setContextPath("/bigapp");
+        request.setServletPath("/servlet");
+        request.setScheme("https");
+        request.setServerPort(8443);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        processor.decide(fi, cad);
+
+        assertFalse(fi.getResponse().isCommitted());
+    }
+
+    public void testDecideDetectsUnacceptableChannel()
+        throws Exception {
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
+        cad.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=true");
+        request.setServerName("localhost");
+        request.setContextPath("/bigapp");
+        request.setServletPath("/servlet");
+        request.setScheme("http");
+        request.setServerPort(8080);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        processor.decide(fi, cad);
+
+        assertTrue(fi.getResponse().isCommitted());
+    }
+
+    public void testDecideRejectsNulls() throws Exception {
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        processor.afterPropertiesSet();
+
+        try {
+            processor.decide(null, null);
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGettersSetters() {
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        assertEquals("REQUIRES_SECURE_CHANNEL", processor.getSecureKeyword());
+        processor.setSecureKeyword("X");
+        assertEquals("X", processor.getSecureKeyword());
+
+        assertTrue(processor.getEntryPoint() != null);
+        processor.setEntryPoint(null);
+        assertTrue(processor.getEntryPoint() == null);
+    }
+
+    public void testMissingEntryPoint() throws Exception {
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        processor.setEntryPoint(null);
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("entryPoint required", expected.getMessage());
+        }
+    }
+
+    public void testMissingSecureChannelKeyword() throws Exception {
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        processor.setSecureKeyword(null);
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("secureKeyword required", expected.getMessage());
+        }
+
+        processor.setSecureKeyword("");
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("secureKeyword required", expected.getMessage());
+        }
+    }
+
+    public void testSupports() {
+        SecureChannelProcessor processor = new SecureChannelProcessor();
+        assertTrue(processor.supports(
+                new SecurityConfig("REQUIRES_SECURE_CHANNEL")));
+        assertFalse(processor.supports(null));
+        assertFalse(processor.supports(new SecurityConfig("NOT_SUPPORTED")));
+    }
+}

+ 71 - 59
docs/reference/src/index.xml

@@ -522,20 +522,17 @@
         Acegi Security System for Spring use this class. Refer to the Filters
         section to learn more about this bean.</para>
 
-        <para>In the application context you will need to configure four
+        <para>In the application context you will need to configure three
         beans:</para>
 
         <programlisting>&lt;bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"&gt;
   &lt;property name="filterSecurityInterceptor"&gt;&lt;ref bean="filterInvocationInterceptor"/&gt;&lt;/property&gt;
   &lt;property name="authenticationEntryPoint"&gt;&lt;ref bean="authenticationEntryPoint"/&gt;&lt;/property&gt;
-  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
 &lt;/bean&gt;
 
 &lt;bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"&gt;
   &lt;property name="loginFormUrl"&gt;&lt;value&gt;/acegilogin.jsp&lt;/value&gt;&lt;/property&gt;
   &lt;property name="forceHttps"&gt;&lt;value&gt;false&lt;/value&gt;&lt;/property&gt;
-  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
-  &lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
 &lt;/bean&gt;
 
 &lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
@@ -549,12 +546,6 @@
       \A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER
     &lt;/value&gt;
   &lt;/property&gt;
-&lt;/bean&gt;
-
-&lt;!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() --&gt;
-&lt;bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl"&gt;
-  &lt;property name="alwaysHttpPort"&gt;&lt;value&gt;8080&lt;/value&gt;&lt;/property&gt;
-  &lt;property name="alwaysHttpsPort"&gt;&lt;value&gt;8443&lt;/value&gt;&lt;/property&gt;
 &lt;/bean&gt;</programlisting>
 
         <para>The <literal>AuthenticationEntryPoint</literal> will be called
@@ -573,13 +564,6 @@
         properties related to forcing the use of HTTPS, so please refer to the
         JavaDocs if you require this.</para>
 
-        <para>The <literal>PortResolver</literal> is used to inspect a HTTP
-        request and determine the server port it was received on. Generally
-        this means using <literal>ServletRequest.getServerPort()</literal>,
-        although implementations can be forced to always return particular
-        ports (based on the transport protocol), as shown in the example
-        above. </para>
-
         <para>The <literal>PortMapper</literal> provides information on which
         HTTPS ports correspond to which HTTP ports. This is used by the
         <literal>AuthenticationProcessingFilterEntryPoint</literal> and
@@ -2756,10 +2740,12 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
 
         <para>In addition to coordinating the authentication and authorization
         requirements of your application, the Acegi Security System for Spring
-        is also able to ensure web requests are received using an appropriate
-        transport. If your application has many security requirements, you'll
-        probably want to use HTTPS as the transport, whilst less secure pages
-        can use the unencrypted HTTP transport.</para>
+        is also able to ensure unauthenticated web requests have certain
+        properties. These properties may include being of a particular
+        transport type, having a particular <literal>HttpSession</literal>
+        attribute set and so on. The most common requirement is for your web
+        requests to be received using a particular transport protocol, such as
+        HTTPS.</para>
 
         <para>An important issue in considering transport security is that of
         session hijacking. Your web container manages a
@@ -2809,30 +2795,28 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
 
         <para><programlisting>&lt;bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter"&gt;
   &lt;property name="channelDecisionManager"&gt;&lt;ref bean="channelDecisionManager"/&gt;&lt;/property&gt;
-  &lt;property name="secureChannelEntryPoint"&gt;&lt;ref bean="secureChannelEntryPoint"/&gt;&lt;/property&gt;
-  &lt;property name="insecureChannelEntryPoint"&gt;&lt;ref bean="insecureChannelEntryPoint"/&gt;&lt;/property&gt;
   &lt;property name="filterInvocationDefinitionSource"&gt;
     &lt;value&gt;
       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
       \A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
       \A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL
-      \A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL
+      \A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL	
       \A.*\Z=REQUIRES_INSECURE_CHANNEL
     &lt;/value&gt;
   &lt;/property&gt;
 &lt;/bean&gt;
 
-&lt;bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/&gt;
-
-&lt;bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint"&gt;
-  &lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
-  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
+&lt;bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"&gt;
+  &lt;property name="channelProcessors"&gt;
+    &lt;list&gt;
+      &lt;ref bean="secureChannelProcessor"/&gt;
+      &lt;ref bean="insecureChannelProcessor"/&gt;
+    &lt;/list&gt;
+  &lt;/property&gt;
 &lt;/bean&gt;
 
-&lt;bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint"&gt;
-  &lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
-  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
-&lt;/bean&gt;</programlisting></para>
+&lt;bean id="secureChannelProcessor" class="net.sf.acegisecurity.securechannel.SecureChannelProcessor"/&gt;
+&lt;bean id="insecureChannelProcessor" class="net.sf.acegisecurity.securechannel.InsecureChannelProcessor"/&gt;</programlisting></para>
 
         <para>Like <literal>FilterSecurityInterceptor</literal>, Apache Ant
         style paths are also supported by the
@@ -2843,36 +2827,41 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
         attributes that apply. It then delegates to the
         <literal>ChannelDecisionManager</literal>. The default implementation,
         <literal>ChannelDecisionManagerImpl</literal>, should suffice in most
-        cases. It simply throws a
-        <literal>SecureChannelRequiredException</literal> or
-        <literal>InsecureChannelRequiredException</literal> if the request's
-        transport channel carries too little or too much security
-        respectively. </para>
-
-        <para>The <literal>ChannelProcessingFilter</literal> will detect the
-        <literal>SecureChannelRequiredException</literal> or
-        <literal>InsecureChannelRequiredException</literal> and delegate to
-        the <literal>secureChannelEntryPoint</literal> or
-        <literal>insecureChannelEntryPoint</literal> respectively. These entry
-        points implement the <literal>ChannelEntryPoint</literal> interface,
-        which allows the implementation to perform a redirect or take similar
-        action. The included <literal>RetryWithHttpsEntryPoint</literal> and
-        <literal>RetryWithHttpEntryPoint</literal> implementations simply
-        perform a redirect.</para>
+        cases. It simply delegates through the list of configured
+        <literal>ChannelProcessor</literal> instances. A
+        <literal>ChannelProcessor</literal> will review the request, and if it
+        is unhappy with the request (eg it was received across the incorrect
+        transport protocol), it will perform a redirect, throw an exception or
+        take whatever other action is appropriate.</para>
+
+        <para>Included with the Acegi Security System for Spring are two
+        concrete <literal>ChannelProcessor</literal> implementations:
+        <literal>SecureChannelProcessor</literal> ensures requests with a
+        configuration attribute of <literal>REQUIRES_SECURE_CHANNEL</literal>
+        are received over HTTPS, whilst
+        <literal>InsecureChannelProcessor</literal> ensures requests with a
+        configuration attribute of
+        <literal>REQUIRES_INSECURE_CHANNEL</literal> are received over HTTP.
+        Both implementations delegate to a
+        <literal>ChannelEntryPoint</literal> if the required transport
+        protocol is not used. The two <literal>ChannelEntryPoint</literal>
+        implementations included with Acegi Security simply redirect the
+        request to HTTP and HTTPS as appropriate. Appropriate defaults are
+        assigned to the <literal>ChannelProcessor</literal> implementations
+        for the configuration attribute keywords they respond to and the
+        <literal>ChannelEntryPoint</literal> they delegate to, although you
+        have the ability to override these using the application
+        context.</para>
 
         <para>Note that the redirections are absolute (eg
         http://www.company.com:8080/app/page), not relative (eg /app/page).
         During testing it was discovered that Internet Explorer 6 Service Pack
-        1 appears to have a bug whereby it does not respond correctly to a
-        redirection instruction which also changes the port to use.
-        Accordingly, absolute URLs are used in conjunction with the
-        <literal>PortResolver</literal> interface to overcome this issue. The
-        <literal>PortResolverImpl</literal> is the included implementation,
-        and is capable of determining the port a request was received on
-        either from the <literal>ServletRequest.getServerPort()</literal>
-        method or from properties defined in the application context. Please
-        refer to the JavaDocs for <literal>PortResolverImpl</literal> for
-        further details.</para>
+        1 has a bug whereby it does not respond correctly to a redirection
+        instruction which also changes the port to use. Accordingly, absolute
+        URLs are used in conjunction with bug detection logic in the
+        <literal>PortResolverImpl</literal> that is wired up by default to
+        many Acegi Security beans. Please refer to the JavaDocs for
+        <literal>PortResolverImpl</literal> for further details.</para>
       </sect2>
 
       <sect2 id="security-channels-usage">
@@ -2885,6 +2874,29 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
         <literal>web.xml</literal> <literal>&lt;welcome-file&gt;</literal> or
         a well-known home page URL), but once this is done the filter will
         perform redirects as defined by your application context.</para>
+
+        <para>You can also add your own <literal>ChannelProcessor</literal>
+        implementations to the <literal>ChannelDecisionManagerImpl</literal>.
+        For example, you might set a <literal>HttpSession</literal> attribute
+        when a human user is detected via a "enter the contents of this
+        graphic" procedure. Your <literal>ChannelProcessor</literal> would
+        respond to say <literal>REQUIRES_HUMAN_USER</literal> configuration
+        attributes and redirect to an appropriate entry point to start the
+        human user validation process if the <literal>HttpSession</literal>
+        attribute is not currently set. </para>
+
+        <para>To decide whether a security check belongs in a
+        <literal>ChannelProcessor</literal> or an
+        <literal>AccessDecisionVoter</literal>, remember that the former is
+        designed to handle unauthenticated requests, whilst the latter is
+        designed to handle authenticated requests. The latter therefore has
+        access to the granted authorities of the authenticated principal. In
+        addition, problems detected by a <literal>ChannelProcessor</literal>
+        will generally cause a HTTP/HTTPS redirection so its requirements can
+        be met, whilst problems detected by an
+        <literal>AccessDecisionVoter</literal> will ultimately result in an
+        <literal>AccessDeniedException</literal> (depending on the governing
+        <literal>AccessDecisionManager</literal>).</para>
       </sect2>
     </sect1>
 

+ 9 - 20
samples/contacts/etc/cas/applicationContext.xml

@@ -163,8 +163,6 @@
 	
 	<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
 		<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
-		<property name="secureChannelEntryPoint"><ref bean="secureChannelEntryPoint"/></property>
-		<property name="insecureChannelEntryPoint"><ref bean="insecureChannelEntryPoint"/></property>
  		<property name="filterInvocationDefinitionSource">
 			<value>
 			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
@@ -175,17 +173,17 @@
 		</property>
 	</bean>
 
-	<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/>
-
-	<bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint">
-		<property name="portMapper"><ref bean="portMapper"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
+	<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl">
+	    <property name="channelProcessors">
+      		<list>
+ 	        	<ref bean="secureChannelProcessor"/>
+        		<ref bean="insecureChannelProcessor"/>
+     		</list>
+	    </property>
 	</bean>
 
-	<bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint">
-		<property name="portMapper"><ref bean="portMapper"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
-	</bean>
+	<bean id="secureChannelProcessor" class="net.sf.acegisecurity.securechannel.SecureChannelProcessor"/>
+	<bean id="insecureChannelProcessor" class="net.sf.acegisecurity.securechannel.InsecureChannelProcessor"/>
 
 	<!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
@@ -199,7 +197,6 @@
 	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
 		<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
 		<property name="authenticationEntryPoint"><ref bean="casProcessingFilterEntryPoint"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
 	</bean>
 
 	<bean id="casProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
@@ -207,14 +204,6 @@
 		<property name="serviceProperties"><ref bean="serviceProperties"/></property>
 	</bean>
 
-	<bean id="portMapper" class="net.sf.acegisecurity.util.PortMapperImpl"/>
-	
-	<!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() -->
-	<bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl">
-		<property name="alwaysHttpPort"><value>8080</value></property>
-		<property name="alwaysHttpsPort"><value>8443</value></property>
-	</bean>
-
 	<bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
    		<property name="allowIfAllAbstainDecisions"><value>false</value></property>
 		<property name="decisionVoters">

+ 9 - 22
samples/contacts/etc/filter/applicationContext.xml

@@ -140,8 +140,6 @@
 
 	<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
 		<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
-		<property name="secureChannelEntryPoint"><ref bean="secureChannelEntryPoint"/></property>
-		<property name="insecureChannelEntryPoint"><ref bean="insecureChannelEntryPoint"/></property>
  		<property name="filterInvocationDefinitionSource">
 			<value>
 			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
@@ -153,17 +151,17 @@
 		</property>
 	</bean>
 
-	<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/>
-
-	<bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint">
-		<property name="portMapper"><ref bean="portMapper"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
+	<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl">
+	    <property name="channelProcessors">
+      		<list>
+ 	        	<ref bean="secureChannelProcessor"/>
+        		<ref bean="insecureChannelProcessor"/>
+     		</list>
+	    </property>
 	</bean>
 
-	<bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint">
-		<property name="portMapper"><ref bean="portMapper"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
-	</bean>
+	<bean id="secureChannelProcessor" class="net.sf.acegisecurity.securechannel.SecureChannelProcessor"/>
+	<bean id="insecureChannelProcessor" class="net.sf.acegisecurity.securechannel.InsecureChannelProcessor"/>
 
 	<!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
@@ -177,22 +175,11 @@
 	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
 		<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
 		<property name="authenticationEntryPoint"><ref bean="authenticationProcessingFilterEntryPoint"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
 	</bean>
 
 	<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
 		<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
 		<property name="forceHttps"><value>false</value></property>
-		<property name="portMapper"><ref bean="portMapper"/></property>
-		<property name="portResolver"><ref bean="portResolver"/></property>
-	</bean>
-
-	<bean id="portMapper" class="net.sf.acegisecurity.util.PortMapperImpl"/>
-	
-	<!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() -->
-	<bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl">
-		<property name="alwaysHttpPort"><value>8080</value></property>
-		<property name="alwaysHttpsPort"><value>8443</value></property>
 	</bean>
 
 	<bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">

+ 2 - 2
samples/contacts/etc/filter/web.xml

@@ -67,12 +67,12 @@
 	
 	<!-- Remove the comments from the following <filter-mapping> if you'd
 	     like to ensure secure URLs are only available over HTTPS -->
-    <!--
+ 
     <filter-mapping>
       <filter-name>Acegi Channel Processing Filter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
-	-->
+
 	
     <filter-mapping>
       <filter-name>Acegi Authentication Processing Filter</filter-name>