diff --git a/changelog.txt b/changelog.txt index 634de7cf23..c715c7a929 100644 --- a/changelog.txt +++ b/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 diff --git a/core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManager.java b/core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManager.java index 6547af3193..311ee5c489 100644 --- a/core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManager.java +++ b/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. + * + *

+ * 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}. + *

* * @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 ChannelDecisionManager is able to + * process the passed ConfigAttribute. + * + *

+ * This allows the ChannelProcessingFilter to check every + * configuration attribute can be consumed by the configured + * ChannelDecisionManager. + *

+ * + * @param attribute a configuration attribute that has been configured + * against the ChannelProcessingFilter + * + * @return true if this ChannelDecisionManager can support the + * passed configuration attribute + */ + public boolean supports(ConfigAttribute attribute); } diff --git a/core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManagerImpl.java b/core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManagerImpl.java index a2a93d0dce..a1277033ec 100644 --- a/core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManagerImpl.java +++ b/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; /** + * Implementation of {@link ChannelDecisionManager}. + * *

- * Ensures configuration attribute requested channel security is present by - * review of HttpServletRequest.isSecure() responses. + * Iterates through each configured {@link ChannelProcessor}. If a + * ChannelProcessor has any issue with the security of the + * request, it should cause a redirect, exception or whatever other action is + * appropriate for the ChannelProcessor implementation. *

* *

- * The class responds to two and only two case-sensitive keywords: {@link - * #getInsecureKeyword()} and {@link #getSecureKeyword}. If either of these - * keywords are detected, HttpServletRequest.isSecure() is used - * to determine the channel security offered. If the channel security differs - * from that requested by the keyword, the relevant exception is thrown. - *

- * - *

- * If both the secureKeyword and insecureKeyword - * 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 should indicate one configuration attribute or the other - * (not both). - *

- * - *

- * The default secureKeyword and insecureKeyword is - * REQUIRES_SECURE_CHANNEL and - * REQUIRES_INSECURE_CHANNEL respectively. + * Once any response is committed (ie a redirect is written to the response + * object), the ChannelDecisionManagerImpl will not iterate + * through any further ChannelProcessors. *

* * @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); + + 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"); + } + } + + this.channelProcessors = newList; } - public String getInsecureKeyword() { - return insecureKeyword; - } - - public void setSecureKeyword(String secureKeyword) { - this.secureKeyword = secureKeyword; - } - - 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"); - } - - Iterator iter = config.getConfigAttributes(); + ConfigAttributeDefinition config) throws IOException, ServletException { + 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"); - } - } + processor.decide(invocation, config); - 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"); - } + if (invocation.getResponse().isCommitted()) { + break; } } } + + public boolean supports(ConfigAttribute attribute) { + Iterator iter = this.channelProcessors.iterator(); + + while (iter.hasNext()) { + ChannelProcessor processor = (ChannelProcessor) iter.next(); + + if (processor.supports(attribute)) { + return true; + } + } + + return false; + } + + private void checkIfValidList(List listToCheck) { + if ((listToCheck == null) || (listToCheck.size() == 0)) { + throw new IllegalArgumentException( + "A list of ChannelProcessors is required"); + } + } } diff --git a/core/src/main/java/org/acegisecurity/securechannel/ChannelEntryPoint.java b/core/src/main/java/org/acegisecurity/securechannel/ChannelEntryPoint.java index 5dfc4da6a7..bcb8881249 100644 --- a/core/src/main/java/org/acegisecurity/securechannel/ChannelEntryPoint.java +++ b/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. * *

- * Depending on the implementation, a secure or insecure channel will be - * launched. + * ChannelProcessors can elect to launch a new web channel + * directly, or they can delegate to another class. The + * ChannelEntryPoint is a pluggable interface to assist + * ChannelProcessors in performing this delegation. *

* * @author Ben Alex @@ -42,13 +44,10 @@ public interface ChannelEntryPoint { *

* Implementations should modify the headers on the * ServletResponse 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. *

* - * @param request that resulted in a - * SecureChannelRequiredException or - * InsecureChannelRequiredException + * @param request that a ChannelProcessor has rejected * @param response so that the user agent can begin using a new channel */ public void commence(ServletRequest request, ServletResponse response) diff --git a/core/src/main/java/org/acegisecurity/securechannel/ChannelProcessingFilter.java b/core/src/main/java/org/acegisecurity/securechannel/ChannelProcessingFilter.java index 8efef64f4c..92be0da303 100644 --- a/core/src/main/java/org/acegisecurity/securechannel/ChannelProcessingFilter.java +++ b/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; *

* *

+ * Delegates the actual channel security decisions and necessary actions to the + * configured {@link ChannelDecisionManager}. If a response is committed by + * the ChannelDecisionManager, the filter chain will not proceed. + *

+ * + *

* Do not use this class directly. Instead configure * web.xml 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; } - if (insecureChannelEntryPoint == null) { + 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 (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; } } diff --git a/core/src/main/java/org/acegisecurity/securechannel/ChannelProcessor.java b/core/src/main/java/org/acegisecurity/securechannel/ChannelProcessor.java new file mode 100644 index 0000000000..82e072cb51 --- /dev/null +++ b/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. + * + *

+ * ChannelProcessor implementations are iterated by the {@link + * ChannelDecisionManagerImpl}. + *

+ * + *

+ * 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. + *

+ * + * @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 ChannelProcessor is able to process + * the passed ConfigAttribute. + * + *

+ * This allows the ChannelProcessingFilter to check every + * configuration attribute can be consumed by the configured + * ChannelDecisionManager. + *

+ * + * @param attribute a configuration attribute that has been configured + * against the ChannelProcessingFilter + * + * @return true if this ChannelProcessor can support the + * passed configuration attribute + */ + public boolean supports(ConfigAttribute attribute); +} diff --git a/core/src/main/java/org/acegisecurity/securechannel/InsecureChannelProcessor.java b/core/src/main/java/org/acegisecurity/securechannel/InsecureChannelProcessor.java new file mode 100644 index 0000000000..23c2204c2d --- /dev/null +++ b/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; + + +/** + *

+ * Ensures channel security is inactive by review of + * HttpServletRequest.isSecure() responses. + *

+ * + *

+ * The class responds to one case-sensitive keyword, {@link + * #getInsecureKeyword}. If this keyword is detected, + * HttpServletRequest.isSecure() is used to determine the channel + * security offered. If channel security is present, the configured + * ChannelEntryPoint is called. By default the entry point is + * {@link RetryWithHttpEntryPoint}. + *

+ * + *

+ * The default insecureKeyword is + * REQUIRES_INSECURE_CHANNEL. + *

+ * + * @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; + } + } +} diff --git a/core/src/main/java/org/acegisecurity/securechannel/InsecureChannelRequiredException.java b/core/src/main/java/org/acegisecurity/securechannel/InsecureChannelRequiredException.java deleted file mode 100644 index 16838d6f49..0000000000 --- a/core/src/main/java/org/acegisecurity/securechannel/InsecureChannelRequiredException.java +++ /dev/null @@ -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 InsecureChannelRequiredException with the - * specified message. - * - * @param msg the detail message. - */ - public InsecureChannelRequiredException(String msg) { - super(msg); - } - - /** - * Constructs an InsecureChannelRequiredException 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); - } -} diff --git a/core/src/main/java/org/acegisecurity/securechannel/SecureChannelProcessor.java b/core/src/main/java/org/acegisecurity/securechannel/SecureChannelProcessor.java new file mode 100644 index 0000000000..4c9367f43b --- /dev/null +++ b/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; + + +/** + *

+ * Ensures channel security is active by review of + * HttpServletRequest.isSecure() responses. + *

+ * + *

+ * The class responds to one case-sensitive keyword, {@link #getSecureKeyword}. + * If this keyword is detected, HttpServletRequest.isSecure() is + * used to determine the channel security offered. If channel security is not + * present, the configured ChannelEntryPoint is called. By + * default the entry point is {@link RetryWithHttpsEntryPoint}. + *

+ * + *

+ * The default secureKeyword is + * REQUIRES_SECURE_CHANNEL. + *

+ * + * @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; + } + } +} diff --git a/core/src/main/java/org/acegisecurity/securechannel/SecureChannelRequiredException.java b/core/src/main/java/org/acegisecurity/securechannel/SecureChannelRequiredException.java deleted file mode 100644 index 62ed5410b6..0000000000 --- a/core/src/main/java/org/acegisecurity/securechannel/SecureChannelRequiredException.java +++ /dev/null @@ -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 SecureChannelRequiredException with the - * specified message. - * - * @param msg the detail message. - */ - public SecureChannelRequiredException(String msg) { - super(msg); - } - - /** - * Constructs a SecureChannelRequiredException 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); - } -} diff --git a/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java b/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java index 61c0db7f82..3112c05ebc 100644 --- a/core/src/test/java/org/acegisecurity/MockHttpServletResponse.java +++ b/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) { diff --git a/core/src/test/java/org/acegisecurity/securechannel/ChannelDecisionManagerImplTests.java b/core/src/test/java/org/acegisecurity/securechannel/ChannelDecisionManagerImplTests.java index 96f748ea39..8395bb8c43 100644 --- a/core/src/test/java/org/acegisecurity/securechannel/ChannelDecisionManagerImplTests.java +++ b/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()); - } - - cdm.setInsecureKeyword(null); - - try { - cdm.afterPropertiesSet(); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - assertEquals("insecureKeyword required", expected.getMessage()); + assertEquals("A list of ChannelProcessors is required", + expected.getMessage()); } } - public void testDetectsInvalidSecureKeyword() throws Exception { + public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() + throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - cdm.setSecureKeyword(""); + 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("secureKeyword required", expected.getMessage()); - } - - cdm.setSecureKeyword(null); - - try { - cdm.afterPropertiesSet(); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - assertEquals("secureKeyword required", expected.getMessage()); - } - } - - public void testDetectsNullsPassedToMainMethod() { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - - try { - cdm.decide(null, new ConfigAttributeDefinition()); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - assertEquals("Nulls cannot be provided", expected.getMessage()); - } - - 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()); - } - } - - public void testDetectsWhenInsecureChannelNeededAndInsecureSchemeUsed() { - ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); - attr.addConfigAttribute(new SecurityConfig( - "SOME_CONFIG_ATTRIBUTE_TO_IGNORE")); - attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL")); - - MockHttpServletRequest request = new MockHttpServletRequest("foo=bar"); - request.setScheme("http"); - - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - cdm.decide(new FilterInvocation(request, new MockHttpServletResponse(), - new MockFilterChain()), attr); - assertTrue(true); - } - - public void testDetectsWhenInsecureChannelNeededAndSecureSchemeUsed() { - ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); - attr.addConfigAttribute(new SecurityConfig( - "SOME_CONFIG_ATTRIBUTE_TO_IGNORE")); - attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL")); - - MockHttpServletRequest request = new MockHttpServletRequest("foo=bar"); - request.setScheme("https"); - - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - - try { - cdm.decide(new FilterInvocation(request, - new MockHttpServletResponse(), new MockFilterChain()), attr); - } catch (InsecureChannelRequiredException expected) { assertTrue(true); } } - public void testDetectsWhenSecureChannelNeeded() { - ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); - attr.addConfigAttribute(new SecurityConfig( - "SOME_CONFIG_ATTRIBUTE_TO_IGNORE")); - attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL")); - - MockHttpServletRequest request = new MockHttpServletRequest("foo=bar"); - request.setScheme("http"); - + public void testCannotSetNullChannelProcessorsList() + throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); try { - cdm.decide(new FilterInvocation(request, - new MockHttpServletResponse(), new MockFilterChain()), attr); - } catch (SecureChannelRequiredException expected) { - assertTrue(true); + cdm.setChannelProcessors(null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("A list of ChannelProcessors is required", + expected.getMessage()); } } - public void testGetterSetters() throws Exception { + 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(); - assertEquals("REQUIRES_INSECURE_CHANNEL", cdm.getInsecureKeyword()); - assertEquals("REQUIRES_SECURE_CHANNEL", cdm.getSecureKeyword()); - cdm.setInsecureKeyword("MY_INSECURE"); - cdm.setSecureKeyword("MY_SECURE"); + MockHttpServletRequest request = new MockHttpServletRequest("not used"); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain(); + FilterInvocation fi = new FilterInvocation(request, response, chain); - assertEquals("MY_INSECURE", cdm.getInsecureKeyword()); - assertEquals("MY_SECURE", cdm.getSecureKeyword()); + ConfigAttributeDefinition cad = new ConfigAttributeDefinition(); + cad.addConfigAttribute(new SecurityConfig("xyz")); + + cdm.decide(fi, cad); + assertTrue(fi.getResponse().isCommitted()); } - public void testIgnoresOtherConfigAttributes() { - ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); - attr.addConfigAttribute(new SecurityConfig("XYZ")); - + public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() + throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"), - new MockHttpServletResponse(), new MockFilterChain()), attr); - assertTrue(true); + 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("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( + "SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT")); + + cdm.decide(fi, cad); + assertFalse(fi.getResponse().isCommitted()); + } + + 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(); + + 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(); + assertNull(cdm.getChannelProcessors()); + + 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); + + assertEquals(list, cdm.getChannelProcessors()); + } + + public void testStartupFailsWithEmptyChannelProcessorsList() + throws Exception { + ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); + + try { + cdm.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("A list of ChannelProcessors is required", + expected.getMessage()); + } + } + + //~ Inner Classes ========================================================== + + private class MockChannelProcessor implements ChannelProcessor { + private String configAttribute; + private boolean failIfCalled; + + public MockChannelProcessor(String configAttribute, boolean failIfCalled) { + this.configAttribute = configAttribute; + this.failIfCalled = failIfCalled; + } + + private MockChannelProcessor() { + super(); + } + + public void decide(FilterInvocation invocation, + ConfigAttributeDefinition config) + throws IOException, ServletException { + Iterator iter = config.getConfigAttributes(); + + if (failIfCalled) { + fail("Should not have called this channel processor"); + } + + while (iter.hasNext()) { + ConfigAttribute attr = (ConfigAttribute) iter.next(); + + if (attr.equals(configAttribute)) { + invocation.getHttpResponse().sendRedirect("/redirected"); + + return; + } + } + } + + public boolean supports(ConfigAttribute attribute) { + if (attribute.getAttribute().equals(configAttribute)) { + return true; + } else { + return false; + } + } } } -; diff --git a/core/src/test/java/org/acegisecurity/securechannel/ChannelProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/securechannel/ChannelProcessingFilterTests.java index 0f962b615d..d79f0d14c6 100644 --- a/core/src/test/java/org/acegisecurity/securechannel/ChannelProcessingFilterTests.java +++ b/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() - 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")); - - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", - attr); - - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setInsecureChannelEntryPoint(new MockEntryPoint(false)); - filter.setSecureChannelEntryPoint(new MockEntryPoint(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()); + + ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); + attr.addConfigAttribute(new SecurityConfig("MOCK")); + + MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", + attr, true); + filter.setFilterInvocationDefinitionSource(fids); 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,36 +89,116 @@ public class ChannelProcessingFilterTests extends TestCase { } } - public void testDetectsMissingInsecureChannelEntryPoint() + public void testDetectsSupportedConfigAttribute() throws Exception { + ChannelProcessingFilter filter = new ChannelProcessingFilter(); + filter.setChannelDecisionManager(new MockChannelDecisionManager(false, + "SUPPORTS_MOCK_ONLY")); + + 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 testDetectsUnsupportedConfigAttribute() 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")); + + 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("insecureChannelEntryPoint must be specified", - expected.getMessage()); + assertTrue(expected.getMessage().startsWith("Unsupported configuration attributes:")); } } - public void testDetectsMissingSecureChannelEntryPoint() + public void testDoFilterWhenManagerDoesCommitResponse() throws Exception { ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint()); - filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap()); - filter.setChannelDecisionManager(new ChannelDecisionManagerImpl()); + filter.setChannelDecisionManager(new MockChannelDecisionManager(true, + "SOME_ATTRIBUTE")); - try { - filter.afterPropertiesSet(); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { - assertEquals("secureChannelEntryPoint must be specified", - expected.getMessage()); - } + ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); + attr.addConfigAttribute(new SecurityConfig("SOME_ATTRIBUTE")); + + MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", + attr, true); + + 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 testDoFilterWhenManagerDoesNotCommitResponse() + throws Exception { + ChannelProcessingFilter filter = new ChannelProcessingFilter(); + filter.setChannelDecisionManager(new MockChannelDecisionManager(false, + "SOME_ATTRIBUTE")); + + ConfigAttributeDefinition attr = new ConfigAttributeDefinition(); + attr.addConfigAttribute(new SecurityConfig("SOME_ATTRIBUTE")); + + MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", + attr, true); + + filter.setFilterInvocationDefinitionSource(fids); + + MockHttpServletRequest request = new MockHttpServletRequest("info=now"); + request.setServletPath("/path"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain chain = new MockFilterChain(true); + + filter.doFilter(request, response, chain); + assertTrue(true); + } + + public void testDoFilterWhenNullConfigAttributeReturned() + throws Exception { + ChannelProcessingFilter filter = new ChannelProcessingFilter(); + 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); + + filter.doFilter(request, response, chain); + assertTrue(true); } public void testDoFilterWithNonHttpServletRequestDetected() @@ -192,91 +227,55 @@ public class ChannelProcessingFilterTests extends TestCase { } } - public void testDoesNotInterruptRequestsWithCorrectChannelSecurity() - throws Exception { + 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("REQUIRES_SECURE_CHANNEL")); + attr.addConfigAttribute(new SecurityConfig("MOCK")); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", - attr); + attr, false); - 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); - - filter.doFilter(request, response, chain); - assertTrue(true); - } - - public void testDoesNotInterruptRequestsWithNoConfigAttribute() - throws Exception { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint()); - filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint()); - filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap()); - filter.setChannelDecisionManager(new ChannelDecisionManagerImpl()); - - MockHttpServletRequest request = new MockHttpServletRequest("info=now"); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain chain = new MockFilterChain(true); - - filter.doFilter(request, response, chain); - assertTrue(true); - } - - public void testGetterSetters() { - 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); - } - public void testLifecycle() throws Exception { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint()); - filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint()); - filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap()); - filter.setChannelDecisionManager(new ChannelDecisionManagerImpl()); + filter.init(null); filter.afterPropertiesSet(); - - filter.init(new MockFilterConfig()); 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) { diff --git a/core/src/test/java/org/acegisecurity/securechannel/InsecureChannelProcessorTests.java b/core/src/test/java/org/acegisecurity/securechannel/InsecureChannelProcessorTests.java new file mode 100644 index 0000000000..329341c8a4 --- /dev/null +++ b/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"))); + } +} diff --git a/core/src/test/java/org/acegisecurity/securechannel/SecureChannelProcessorTests.java b/core/src/test/java/org/acegisecurity/securechannel/SecureChannelProcessorTests.java new file mode 100644 index 0000000000..b643228d3a --- /dev/null +++ b/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"))); + } +} diff --git a/docs/reference/src/index.xml b/docs/reference/src/index.xml index 3f2590ec33..a7dedc2386 100644 --- a/docs/reference/src/index.xml +++ b/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. - In the application context you will need to configure four + In the application context you will need to configure three beans: <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"> <property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property> <property name="authenticationEntryPoint"><ref bean="authenticationEntryPoint"/></property> - <property name="portResolver"><ref bean="portResolver"/></property> </bean> <bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"> <property name="loginFormUrl"><value>/acegilogin.jsp</value></property> <property name="forceHttps"><value>false</value></property> - <property name="portResolver"><ref bean="portResolver"/></property> - <property name="portMapper"><ref bean="portMapper"/></property> </bean> <bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"> @@ -549,12 +546,6 @@ \A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER </value> </property> -</bean> - -<!-- 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> The AuthenticationEntryPoint 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. - The PortResolver is used to inspect a HTTP - request and determine the server port it was received on. Generally - this means using ServletRequest.getServerPort(), - although implementations can be forced to always return particular - ports (based on the transport protocol), as shown in the example - above. - The PortMapper provides information on which HTTPS ports correspond to which HTTP ports. This is used by the AuthenticationProcessingFilterEntryPoint and @@ -2756,10 +2740,12 @@ $CATALINA_HOME/bin/startup.sh 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. + is also able to ensure unauthenticated web requests have certain + properties. These properties may include being of a particular + transport type, having a particular HttpSession + 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. 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 <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 \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 </value> </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"/> Like FilterSecurityInterceptor, Apache Ant style paths are also supported by the @@ -2843,36 +2827,41 @@ $CATALINA_HOME/bin/startup.sh attributes that apply. It then delegates to the ChannelDecisionManager. The default implementation, ChannelDecisionManagerImpl, should suffice in most - cases. It simply throws a - SecureChannelRequiredException or - InsecureChannelRequiredException if the request's - transport channel carries too little or too much security - respectively. + cases. It simply delegates through the list of configured + ChannelProcessor instances. A + ChannelProcessor 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. - The ChannelProcessingFilter will detect the - SecureChannelRequiredException or - InsecureChannelRequiredException and delegate to - the secureChannelEntryPoint or - insecureChannelEntryPoint respectively. These entry - points implement the ChannelEntryPoint interface, - which allows the implementation to perform a redirect or take similar - action. The included RetryWithHttpsEntryPoint and - RetryWithHttpEntryPoint implementations simply - perform a redirect. + Included with the Acegi Security System for Spring are two + concrete ChannelProcessor implementations: + SecureChannelProcessor ensures requests with a + configuration attribute of REQUIRES_SECURE_CHANNEL + are received over HTTPS, whilst + InsecureChannelProcessor ensures requests with a + configuration attribute of + REQUIRES_INSECURE_CHANNEL are received over HTTP. + Both implementations delegate to a + ChannelEntryPoint if the required transport + protocol is not used. The two ChannelEntryPoint + implementations included with Acegi Security simply redirect the + request to HTTP and HTTPS as appropriate. Appropriate defaults are + assigned to the ChannelProcessor implementations + for the configuration attribute keywords they respond to and the + ChannelEntryPoint they delegate to, although you + have the ability to override these using the application + context. 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 - PortResolver interface to overcome this issue. The - PortResolverImpl is the included implementation, - and is capable of determining the port a request was received on - either from the ServletRequest.getServerPort() - method or from properties defined in the application context. Please - refer to the JavaDocs for PortResolverImpl for - further details. + 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 + PortResolverImpl that is wired up by default to + many Acegi Security beans. Please refer to the JavaDocs for + PortResolverImpl for further details. @@ -2885,6 +2874,29 @@ $CATALINA_HOME/bin/startup.sh web.xml <welcome-file> or a well-known home page URL), but once this is done the filter will perform redirects as defined by your application context. + + You can also add your own ChannelProcessor + implementations to the ChannelDecisionManagerImpl. + For example, you might set a HttpSession attribute + when a human user is detected via a "enter the contents of this + graphic" procedure. Your ChannelProcessor would + respond to say REQUIRES_HUMAN_USER configuration + attributes and redirect to an appropriate entry point to start the + human user validation process if the HttpSession + attribute is not currently set. + + To decide whether a security check belongs in a + ChannelProcessor or an + AccessDecisionVoter, 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 ChannelProcessor + will generally cause a HTTP/HTTPS redirection so its requirements can + be met, whilst problems detected by an + AccessDecisionVoter will ultimately result in an + AccessDeniedException (depending on the governing + AccessDecisionManager). diff --git a/samples/contacts/etc/cas/applicationContext.xml b/samples/contacts/etc/cas/applicationContext.xml index 2c2d71064e..3de67912e4 100644 --- a/samples/contacts/etc/cas/applicationContext.xml +++ b/samples/contacts/etc/cas/applicationContext.xml @@ -163,8 +163,6 @@ - - CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON @@ -175,17 +173,17 @@ - - - - - + + + + + + + - - - - + + @@ -199,7 +197,6 @@ - @@ -207,14 +204,6 @@ - - - - - 8080 - 8443 - - false diff --git a/samples/contacts/etc/filter/applicationContext.xml b/samples/contacts/etc/filter/applicationContext.xml index c18458718c..46193e6980 100644 --- a/samples/contacts/etc/filter/applicationContext.xml +++ b/samples/contacts/etc/filter/applicationContext.xml @@ -140,8 +140,6 @@ - - CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON @@ -153,17 +151,17 @@ - - - - - + + + + + + + - - - - + + @@ -177,22 +175,11 @@ - /acegilogin.jsp false - - - - - - - - - 8080 - 8443 diff --git a/samples/contacts/etc/filter/web.xml b/samples/contacts/etc/filter/web.xml index c3b851e85c..e06afa67fb 100644 --- a/samples/contacts/etc/filter/web.xml +++ b/samples/contacts/etc/filter/web.xml @@ -67,12 +67,12 @@ - + Acegi Authentication Processing Filter