Procházet zdrojové kódy

SEC-2
Refactor the CaptchaChannelProcessor and extract a CaptchaChannelProcessor that is an abstract class and add its implementations.
Jalopy on all java files.

Marc-Antoine Garrigue před 20 roky
rodič
revize
5235727d23
22 změnil soubory, kde provedl 1610 přidání a 1410 odebrání
  1. 68 0
      core/src/main/java/org/acegisecurity/captcha/AlwaysTestAfterMaxRequestsCaptchaChannelProcessor.java
  2. 66 0
      core/src/main/java/org/acegisecurity/captcha/AlwaysTestAfterTimeInMillisCaptchaChannelProcessor.java
  3. 93 0
      core/src/main/java/org/acegisecurity/captcha/AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor.java
  4. 0 371
      core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessor.java
  5. 163 0
      core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessorTemplate.java
  6. 237 182
      core/src/main/java/org/acegisecurity/captcha/CaptchaEntryPoint.java
  7. 36 28
      core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContext.java
  8. 60 55
      core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContextImpl.java
  9. 12 6
      core/src/main/java/org/acegisecurity/captcha/CaptchaServiceProxy.java
  10. 57 44
      core/src/main/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilter.java
  11. 56 0
      core/src/main/java/org/acegisecurity/captcha/TestOnceAfterMaxRequestsCaptchaChannelProcessor.java
  12. 7 1
      core/src/main/java/org/acegisecurity/captcha/package.html
  13. 76 0
      core/src/test/java/org/acegisecurity/captcha/AlwaysTestAfterMaxRequestsCaptchaChannelProcessorTests.java
  14. 98 0
      core/src/test/java/org/acegisecurity/captcha/AlwaysTestAfterTimeInMillisCaptchaChannelProcessorTests.java
  15. 125 0
      core/src/test/java/org/acegisecurity/captcha/AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessorTests.java
  16. 216 0
      core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTemplateTests.java
  17. 0 552
      core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTests.java
  18. 84 97
      core/src/test/java/org/acegisecurity/captcha/CaptchaEntryPointTests.java
  19. 34 37
      core/src/test/java/org/acegisecurity/captcha/CaptchaSecurityContextImplTests.java
  20. 31 33
      core/src/test/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilterTests.java
  21. 7 4
      core/src/test/java/org/acegisecurity/captcha/MockCaptchaServiceProxy.java
  22. 84 0
      core/src/test/java/org/acegisecurity/captcha/TestOnceAfterMaxRequestsCaptchaChannelProcessorTests.java

+ 68 - 0
core/src/main/java/org/acegisecurity/captcha/AlwaysTestAfterMaxRequestsCaptchaChannelProcessor.java

@@ -0,0 +1,68 @@
+/* Copyright 2004, 2005 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.
+ */
+
+/*
+ * Copyright (c) 2005 Your Corporation. All Rights Reserved.
+ */
+package net.sf.acegisecurity.captcha;
+
+/**
+ * <p>
+ * return false if ny CaptchaChannelProcessorTemplate of mapped urls has been
+ * requested more than thresold; <br>
+ * Default keyword : REQUIRES_CAPTCHA_ABOVE_THRESOLD_REQUESTS
+ * </p>
+ *
+ * @author Marc-Antoine Garrigue
+ * @version $Id$
+ */
+public class AlwaysTestAfterMaxRequestsCaptchaChannelProcessor
+    extends CaptchaChannelProcessorTemplate {
+    //~ Static fields/initializers =============================================
+
+    /** Keyword for this channelProcessor */
+    public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_ABOVE_THRESOLD_REQUESTS";
+
+    //~ Constructors ===========================================================
+
+    /**
+     * Constructor
+     */
+    public AlwaysTestAfterMaxRequestsCaptchaChannelProcessor() {
+        super();
+        this.setKeyword(DEFAULT_KEYWORD);
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * Verify wheter the context is valid concerning humanity
+     *
+     * @param context
+     *
+     * @return true if valid, false otherwise
+     */
+    boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
+        if (context.getHumanRestrictedResourcesRequestsCount() < getThresold()) {
+            logger.debug("context is valid : request count < thresold");
+
+            return true;
+        } else {
+            logger.debug("context is not valid : request count > thresold");
+
+            return false;
+        }
+    }
+}

+ 66 - 0
core/src/main/java/org/acegisecurity/captcha/AlwaysTestAfterTimeInMillisCaptchaChannelProcessor.java

@@ -0,0 +1,66 @@
+/* Copyright 2004, 2005 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.captcha;
+
+/**
+ * <p>
+ * return false if thresold is greater than millis since last captcha test has occured;<br>
+ * Default keyword : REQUIRES_CAPTCHA_AFTER_THRESOLD_IN_MILLIS
+ * </p>
+ *
+ * @author Marc-Antoine Garrigue
+ * @version $Id$
+ */
+public class AlwaysTestAfterTimeInMillisCaptchaChannelProcessor
+    extends CaptchaChannelProcessorTemplate {
+    //~ Static fields/initializers =============================================
+
+    /** Keyword for this channelProcessor */
+    public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_AFTER_THRESOLD_IN_MILLIS";
+
+    //~ Constructors ===========================================================
+
+    /**
+     * Constructor
+     */
+    public AlwaysTestAfterTimeInMillisCaptchaChannelProcessor() {
+        super();
+        this.setKeyword(DEFAULT_KEYWORD);
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * Verify wheter the context is valid concerning humanity
+     *
+     * @param context the CaptchaSecurityContext
+     *
+     * @return true if valid, false otherwise
+     */
+    boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
+        if ((System.currentTimeMillis()
+            - context.getLastPassedCaptchaDateInMillis()) < getThresold()) {
+            logger.debug(
+                "context is valid : last passed captcha date - current time < thresold");
+
+            return true;
+        } else {
+            logger.debug(
+                "context is not valid : last passed captcha date - current time > thresold");
+
+            return false;
+        }
+    }
+}

+ 93 - 0
core/src/main/java/org/acegisecurity/captcha/AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor.java

@@ -0,0 +1,93 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * <p>
+ * return false if thresold is lower than average time millis between any
+ * CaptchaChannelProcessorTemplate mapped urls requests and is human;<br>
+ * Default keyword : REQUIRES_CAPTCHA_AFTER_THRESOLD_IN_MILLIS <br>
+ * Note : before first humanity check
+ * </p>
+ *
+ * @author Marc-Antoine Garrigue
+ * @version $Id$
+ */
+public class AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+    extends CaptchaChannelProcessorTemplate {
+    //~ Static fields/initializers =============================================
+
+    /** Keyword for this channelProcessor */
+    public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_BELOW_AVERAGE_TIME_IN_MILLIS_REQUESTS";
+
+    //~ Constructors ===========================================================
+
+    /**
+     * Constructor
+     */
+    public AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor() {
+        super();
+        this.setKeyword(DEFAULT_KEYWORD);
+    }
+
+    //~ Methods ================================================================
+
+    /**
+     * Verify if thresold is &gt; 0
+     *
+     * @throws Exception if false
+     */
+    public void afterPropertiesSet() throws Exception {
+        super.afterPropertiesSet();
+        Assert.isTrue(getThresold() > 0, "thresold must be > 0");
+    }
+
+    /**
+     * Verify wheter the context is valid concerning humanity
+     *
+     * @param context
+     *
+     * @return true if valid, false otherwise
+     */
+    boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
+        int req = context.getHumanRestrictedResourcesRequestsCount();
+        float thresold = getThresold();
+        float duration = System.currentTimeMillis()
+            - context.getLastPassedCaptchaDateInMillis();
+        float average;
+
+        if (req == 0) {
+            average = thresold + 1;
+        } else {
+            average = duration / req;
+        }
+
+        if (context.isHuman() && (average > thresold)) {
+            logger.debug(
+                "context is valid : average time between requests < thresold && is human");
+
+            return true;
+        } else {
+            logger.debug(
+                "context is not valid : request count > thresold or is not human");
+
+            return false;
+        }
+    }
+}

+ 0 - 371
core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessor.java

@@ -1,371 +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.captcha;
-
-import java.io.IOException;
-import java.util.Iterator;
-
-import javax.servlet.ServletException;
-
-import net.sf.acegisecurity.ConfigAttribute;
-import net.sf.acegisecurity.ConfigAttributeDefinition;
-import net.sf.acegisecurity.context.SecurityContextHolder;
-import net.sf.acegisecurity.intercept.web.FilterInvocation;
-import net.sf.acegisecurity.securechannel.ChannelEntryPoint;
-import net.sf.acegisecurity.securechannel.ChannelProcessor;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.util.Assert;
-
-/**
- * <p>
- * Ensures the user has enougth human privileges by review of the
- * {@link net.sf.acegisecurity.captcha.CaptchaSecurityContext}.
- * </p>
- * 
- * <P>
- * The class takes 3 required attributes :
- * <ul>
- * <li>maxRequestsBeforeFirstTest : used by
- * {@link #getRequiresHumanAfterMaxRequestsKeyword()} and
- * {@link #getRequiresHumanAfterMaxMillisKeyword()}<br>
- * default value = 0 (ie first request).</li>
- * <li>maxRequestsBeforeReTest : used by
- * {@link #getRequiresHumanAfterMaxMillisKeyword()}<br>
- * default value = -1 (ie disabled, once in a {@link CaptchaSecurityContext}'s
- * life).</li>
- * <li>maxMillisBeforeReTest: used by
- * {@link #getRequiresHumanAfterMaxMillisKeyword()} <br>
- * default value = -1 (ie disabled, once in a {@link CaptchaSecurityContext}'s
- * life).</li>
- * </ul>
- * The class responds to two case-sensitive keywords :
- * <ul>
- * <li>{@link #getRequiresHumanAfterMaxRequestsKeyword()} <br>
- * default value = <code>REQUIRES_HUMAN_AFTER_MAX_REQUESTS</code> <br>
- * if detected, checks if :
- * <ul>
- * <ul>
- * <li><code>{@link CaptchaSecurityContext#isHuman()} == true</code> </li>
- * <li><b>or</b></li>
- * <li><code>{@link CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()} < maxRequestsBeforeFirstTest</code></b></li>
- * </ul>
- * <li><b>and</b></li>
- * <ul>
- * <li><code>{@link CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()} < maxRequestsBeforeReTest </code></li>
- * <li><b>or</b></li>
- * <li><code>maxRequestsBeforeReTest < 0 </code></b></li>
- * </ul>
- * </ul>
- * </li>
- * 
- * 
- * 
- * 
- * 
- * 
- * 
- * <li>{@link #getRequiresHumanAfterMaxRequestsKeyword()} <br>
- * default value = <code>REQUIRES_HUMAN_AFTER_MAX_MILLIS</code> <br>
- * if detected, checks if :
- * 
- * <ul>
- * <ul>
- * <li><code>{@link CaptchaSecurityContext#isHuman()} == true</code> </li>
- * <li><b>or</b></li>
- * <li><code>{@link CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()} < =maxRequestsBeforeFirstTest</code></b></li>
- * </ul>
- * <li><b>and</b></li>
- * <ul>
- * <li><code>System.currentTimeMillis()-{@link CaptchaSecurityContext#getLastPassedCaptchaDateInMillis()} <= maxMillisBeforeReTest </code></li>
- * <li><b>or</b></li>
- * <li><code>maxMillisBeforeReTest < 0 </code></b></li>
- * </ul>
- * </ul>
- * </li>
- * </ul>
- * </p>
- * 
- * <p>
- * <u>Examples : to ensure an url is accessed only by human that pass a captcha
- * (assuming you are using the
- * {@link net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter})
- * </u><br>
- * <ul>
- * <li>Once in a session and at first request : use the <br>
- * REQUIRES_HUMAN_AFTER_MAX_REQUESTS keyword <br>
- * with a maxRequestsBeforeFirstTest=0<br>
- * and a maxRequestsBeforeReTest=-1<br>
- * </li>
- * <br>
- * &nbsp;
- * <li>Once in a session and only after 3 requests : use the <br>
- * REQUIRES_HUMAN_AFTER_MAX_REQUESTS keyword <br>
- * with a maxRequestsBeforeFirstTest=3</li>
- * and a maxRequestsBeforeReTest=-1<br>
- * <br>
- * &nbsp;
- * <li>Every request and only after 5 requests : use the <br>
- * REQUIRES_HUMAN_AFTER_MAX_REQUESTS <br>
- * with a maxRequestsBeforeReTest=0<br>
- * and a maxRequestsBeforeFirstTest=5</li>
- * <br>
- * &nbsp;
- * <li>Every 3 requests and every minute : use the <br>
- * REQUIRES_HUMAN_AFTER_MAX_MILLIS keywords <br>
- * with a maxMillisBeforeReTest=6000 <br>
- * and a maxRequestsBeforeFirstTest=3</li>
- * <br>
- * &nbsp;
- * <li>Every 20 requests and every hour and only after 100 requests : use the
- * <br>
- * REQUIRES_HUMAN_AFTER_MAX_REQUESTS <br>
- * and the REQUIRES_HUMAN_AFTER_MAX_MILLIS <br>
- * and the REQUIRES_HUMAN_AFTER_MAX_REQUESTS keywords <br>
- * with a maxRequestsBeforeReTest=20 <br>
- * and a maxMillisBeforeReTest=3600000 <br>
- * and a maxRequestsBeforeFirstTest=1000</li>
- * 
- * </ul>
- * 
- * 
- * @author marc antoine Garrigue
- * @version $Id$
- */
-public class CaptchaChannelProcessor implements ChannelProcessor,
-		InitializingBean {
-	// ~ Static fields/initializers
-	// =============================================
-
-	private static final Log logger = LogFactory
-			.getLog(CaptchaChannelProcessor.class);
-
-	private String requiresHumanAfterMaxRequestsKeyword = "REQUIRES_HUMAN_AFTER_MAX_REQUESTS";
-
-	private String requiresHumanAfterMaxMillisKeyword = "REQUIRES_HUMAN_AFTER_MAX_MILLIS";
-
-    private String keywordPrefix = "";
-
-
-    private ChannelEntryPoint entryPoint;
-
-	private int maxRequestsBeforeReTest = -1;
-
-	private int maxRequestsBeforeFirstTest = 0;
-
-	private long maxMillisBeforeReTest = -1;
-
-    public String getKeywordPrefix() {
-        return keywordPrefix;
-    }
-
-    public void setKeywordPrefix(String keywordPrefix) {
-        this.keywordPrefix = keywordPrefix;
-    }
-
-    public String getRequiresHumanAfterMaxMillisKeyword() {
-		return requiresHumanAfterMaxMillisKeyword;
-	}
-
-	public void setRequiresHumanAfterMaxMillisKeyword(
-			String requiresHumanAfterMaxMillis) {
-		this.requiresHumanAfterMaxMillisKeyword = requiresHumanAfterMaxMillis;
-
-	}
-
-	public void setRequiresHumanAfterMaxRequestsKeyword(
-			String requiresHumanAfterMaxRequestsKeyword) {
-		this.requiresHumanAfterMaxRequestsKeyword = requiresHumanAfterMaxRequestsKeyword;
-	}
-
-	public ChannelEntryPoint getEntryPoint() {
-		return entryPoint;
-	}
-
-	public void setEntryPoint(ChannelEntryPoint entryPoint) {
-		this.entryPoint = entryPoint;
-	}
-
-	public int getMaxRequestsBeforeReTest() {
-		return maxRequestsBeforeReTest;
-	}
-
-	public void setMaxRequestsBeforeReTest(int maxRequestsBeforeReTest) {
-		this.maxRequestsBeforeReTest = maxRequestsBeforeReTest;
-	}
-
-	public String getRequiresHumanAfterMaxRequestsKeyword() {
-		return requiresHumanAfterMaxRequestsKeyword;
-	}
-
-	public int getMaxRequestsBeforeFirstTest() {
-		return maxRequestsBeforeFirstTest;
-	}
-
-	public void setMaxRequestsBeforeFirstTest(int maxRequestsBeforeFirstTest) {
-		this.maxRequestsBeforeFirstTest = maxRequestsBeforeFirstTest;
-	}
-
-	public void afterPropertiesSet() throws Exception {
-		Assert.notNull(entryPoint, "entryPoint required");
-	}
-
-	public long getMaxMillisBeforeReTest() {
-		return maxMillisBeforeReTest;
-	}
-
-	public void setMaxMillisBeforeReTest(long maxMillisBeforeReTest) {
-		this.maxMillisBeforeReTest = maxMillisBeforeReTest;
-	}
-
-	public void decide(FilterInvocation invocation,
-			ConfigAttributeDefinition config) throws IOException,
-			ServletException {
-		if ((invocation == null) || (config == null)) {
-			throw new IllegalArgumentException("Nulls cannot be provided");
-		}
-        CaptchaSecurityContext context = null;
-        context = (CaptchaSecurityContext) SecurityContextHolder
-                    .getContext();
-
-        Iterator iter = config.getConfigAttributes();
-		boolean shouldRedirect = false;
-
-		while (iter.hasNext()) {
-			ConfigAttribute attribute = (ConfigAttribute) iter.next();
-			if (supports(attribute)) {
-				logger.debug("supports this attribute : " + attribute);
-				if (!isContextValidForAttribute(context, attribute)) {
-					shouldRedirect = true;
-                }
-            }
-		}
-		if (shouldRedirect) {
-			logger
-					.debug("context is not allowed to access ressource, redirect to captcha entry point");
-			redirectToEntryPoint(invocation);
-		} else {
-			// if we reach this point, we forward the request so
-			// increment it
-			logger
-					.debug("context is allowed to access ressource, increment rectricted ressource requests count ");
-			context.incrementHumanRestrictedRessoucesRequestsCount();
-
-		}
-	}
-
-	private boolean isContextValidForAttribute(CaptchaSecurityContext context,
-			ConfigAttribute attribute) {
-		boolean valid = false;
-		if ((attribute != null) || (attribute.getAttribute() != null)) {
-
-			// test the REQUIRES_HUMAN_AFTER_MAX_REQUESTS keyword
-			if (isKeywordMaxRequest(attribute)) {
-				if (isContextValidConcerningHumanOrFirstTest(context)
-						&& isContextValidConcerningReTest(context)) {
-					valid = true;
-				}
-			}
-
-			// test the REQUIRES_HUMAN_AFTER_MAX_MILLIS keyword
-			if (isKeywordMillis(attribute)) {
-				if (isContextValidConcerningHumanOrFirstTest(context)
-						&& isContextValidConcerningMaxMillis(context)) {
-					valid = true;
-				}
-			}
-
-		}
-		return valid;
-	}
-
-	private boolean isContextValidConcerningHumanOrFirstTest(
-			CaptchaSecurityContext context) {
-		if (context.isHuman()
-				|| context.getHumanRestrictedResourcesRequestsCount() < maxRequestsBeforeFirstTest) {
-			logger
-					.debug("context is valid concerning humanity or request count < maxRequestsBeforeFirstTest");
-
-			return true;
-		} else {
-			logger
-					.debug("context is not valid concerning humanity and request count > maxRequestsBeforeFirstTest");
-			return false;
-		}
-	}
-
-	private boolean isContextValidConcerningReTest(
-			CaptchaSecurityContext context) {
-		if (context.getHumanRestrictedResourcesRequestsCount() < maxRequestsBeforeReTest
-				|| maxRequestsBeforeReTest < 0) {
-			logger.debug("context is valid concerning reTest");
-
-			return true;
-		} else {
-			logger.debug("context is not valid concerning reTest");
-
-			return false;
-		}
-	}
-
-	private boolean isContextValidConcerningMaxMillis(
-			CaptchaSecurityContext context) {
-		if (System.currentTimeMillis()
-				- context.getLastPassedCaptchaDateInMillis() < maxMillisBeforeReTest
-				|| maxMillisBeforeReTest < 0) {
-			logger.debug("context is valid concerning maxMillis");
-
-			return true;
-		} else {
-			logger.debug("context is not valid concerning maxMillis");
-
-			return false;
-		}
-	}
-
-	private void redirectToEntryPoint(FilterInvocation invocation)
-			throws IOException, ServletException {
-		logger
-				.debug("security constraints not repected : redirecting to entry point");
-		entryPoint.commence(invocation.getRequest(), invocation.getResponse());
-		return;
-	}
-
-	public boolean supports(ConfigAttribute attribute) {
-		if ((attribute != null)
-				&& (attribute.getAttribute() != null)
-				&& (isKeywordMaxRequest(attribute) || isKeywordMillis(attribute)
-
-				)) {
-			return true;
-		} else {
-			return false;
-		}
-	}
-
-    private boolean isKeywordMillis(ConfigAttribute attribute) {
-        return attribute
-                .getAttribute().equals(
-                        getKeywordPrefix()+getRequiresHumanAfterMaxMillisKeyword());
-    }
-
-    private boolean isKeywordMaxRequest(ConfigAttribute attribute) {
-        return attribute.getAttribute().equals(
-                getKeywordPrefix()+getRequiresHumanAfterMaxRequestsKeyword());
-    }
-
-}

+ 163 - 0
core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessorTemplate.java

@@ -0,0 +1,163 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import net.sf.acegisecurity.ConfigAttribute;
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.context.SecurityContextHolder;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+import net.sf.acegisecurity.securechannel.ChannelEntryPoint;
+import net.sf.acegisecurity.securechannel.ChannelProcessor;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import javax.servlet.ServletException;
+
+
+/**
+ * <p>
+ * CaptchaChannel template : Ensures the user has enough human privileges by
+ * review of the {@link CaptchaSecurityContext} and using an abstract routine
+ * {@link #isContextValidConcerningHumanity(CaptchaSecurityContext)}
+ * (implemented by sub classes)
+ * </p>
+ * 
+ * <P>
+ * The component uses 2 main parameters for its configuration :
+ * 
+ * <ul>
+ * <li>
+ * a keyword to be mapped to urls in the {@link
+ * net.sf.acegisecurity.securechannel.ChannelProcessingFilter} configuration<br>
+ * default value provided by sub classes.
+ * </li>
+ * <li>
+ * and a thresold : used by the routine {@link
+ * #isContextValidConcerningHumanity(CaptchaSecurityContext)} to evaluate
+ * whether the {@link CaptchaSecurityContext} is valid default value = 0
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @author marc antoine Garrigue
+ * @version $Id$
+ */
+public abstract class CaptchaChannelProcessorTemplate
+    implements ChannelProcessor, InitializingBean {
+    //~ Instance fields ========================================================
+
+    protected Log logger = LogFactory.getLog(this.getClass());
+    private ChannelEntryPoint entryPoint;
+    private String keyword = null;
+    private int thresold = 0;
+
+    //~ Methods ================================================================
+
+    public void setEntryPoint(ChannelEntryPoint entryPoint) {
+        this.entryPoint = entryPoint;
+    }
+
+    public ChannelEntryPoint getEntryPoint() {
+        return entryPoint;
+    }
+
+    public void setKeyword(String keyword) {
+        this.keyword = keyword;
+    }
+
+    public String getKeyword() {
+        return keyword;
+    }
+
+    public void setThresold(int thresold) {
+        this.thresold = thresold;
+    }
+
+    public int getThresold() {
+        return thresold;
+    }
+
+    /**
+     * Verify if entryPoint and keyword are ok
+     *
+     * @throws Exception if not
+     */
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(entryPoint, "entryPoint required");
+        Assert.hasLength(keyword, "keyword required");
+    }
+
+    public void decide(FilterInvocation invocation,
+        ConfigAttributeDefinition config) throws IOException, ServletException {
+        if ((invocation == null) || (config == null)) {
+            throw new IllegalArgumentException("Nulls cannot be provided");
+        }
+
+        CaptchaSecurityContext context = null;
+        context = (CaptchaSecurityContext) SecurityContextHolder.getContext();
+
+        Iterator iter = config.getConfigAttributes();
+
+        while (iter.hasNext()) {
+            ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+            if (supports(attribute)) {
+                logger.debug("supports this attribute : " + attribute);
+
+                if (!isContextValidConcerningHumanity(context)) {
+                    logger.debug(
+                        "context is not allowed to access ressource, redirect to captcha entry point");
+                    redirectToEntryPoint(invocation);
+                } else {
+                    logger.debug(
+                        "has been successfully checked this keyword, increment request count");
+                    context.incrementHumanRestrictedRessoucesRequestsCount();
+                }
+            } else {
+                logger.debug("do not support this attribute");
+            }
+        }
+    }
+
+    public boolean supports(ConfigAttribute attribute) {
+        if ((attribute != null) && (keyword.equals(attribute.getAttribute()))) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    abstract boolean isContextValidConcerningHumanity(
+        CaptchaSecurityContext context);
+
+    private void redirectToEntryPoint(FilterInvocation invocation)
+        throws IOException, ServletException {
+        if (logger.isDebugEnabled()) {
+            logger.debug("context is not valid : redirecting to entry point");
+        }
+
+        entryPoint.commence(invocation.getRequest(), invocation.getResponse());
+    }
+}

+ 237 - 182
core/src/main/java/org/acegisecurity/captcha/CaptchaEntryPoint.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -12,6 +12,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package net.sf.acegisecurity.captcha;
 
 import net.sf.acegisecurity.securechannel.ChannelEntryPoint;
@@ -19,234 +20,278 @@ import net.sf.acegisecurity.util.PortMapper;
 import net.sf.acegisecurity.util.PortMapperImpl;
 import net.sf.acegisecurity.util.PortResolver;
 import net.sf.acegisecurity.util.PortResolverImpl;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.beans.factory.InitializingBean;
+
 import org.springframework.util.Assert;
 
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.net.URLEncoder;
+
+import java.util.Enumeration;
+
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Enumeration;
+
 
 /**
- * The captcha entry point : redirect to the captcha test page. <br/>
- * <p/>
- * This entry point can force the use of SSL : see {@link #getForceHttps()}<br/>
- * <p/>
- * This entry point allows internal OR external redirect : see {@link #setOutsideWebApp(boolean)}<br/>/ Original request
- * can be added to the redirect path using a custom translation : see {@link #setIncludeOriginalRequest(boolean)} <br/>
- * Original request is translated using URLEncoding and the following translation mapping in the redirect url : <ul>
- * <li>original url => {@link #getOriginalRequestUrlParameterName()}</li> <li> If {@link
- * #isIncludeOriginalParameters()}</li> <li>original method => {@link #getOriginalRequestMethodParameterName()} </li>
- * <li>original parameters => {@link #getOriginalRequestParametersParameterName()} </li> <li>The orinial parameters
- * string is contructed using :</li> <ul> <li>a parameter separator {@link #getOriginalRequestParametersSeparator()}
- * </li> <li>a parameter name value pair separator for each parameter {@link #getOriginalRequestParametersNameValueSeparator()}
- * </li> </ul> </ul>
- * <p/>
- * <p/>
- * <p/>
- * <br/> Default values :<br/> forceHttps = false<br/> includesOriginalRequest = true<br/> includesOriginalParameters =
- * false<br/> isOutsideWebApp=false<br/> originalRequestUrlParameterName  ="original_requestUrl" <br/>
- * originalRequestParametersParameterName = "original_request_parameters";<br/>
- * <p/>
- * originalRequestParametersNameValueSeparator = "@@";        <br/>
- * <p/>
- * originalRequestParametersSeparator = ";;";         <br/>
- * <p/>
- * originalRequestMethodParameterName = "original_request_method";  <br/>
- * <p/>
- * urlEncodingCharset = "UTF-8";             <br/>
+ * The captcha entry point : redirect to the captcha test page. <br>
+ * 
+ * <p>
+ * This entry point can force the use of SSL : see {@link #getForceHttps()}<br>
+ * </p>
+ * This entry point allows internal OR external redirect : see {@link #setOutsideWebApp(boolean)}<br>
+ * / Original request can be added to the redirect path using a custom
+ * translation : see {@link #setIncludeOriginalRequest(boolean)}<br>
+ * Original request is translated using URLEncoding and the following
+ * translation mapping in the redirect url :
+ * 
+ * <ul>
+ * <li>
+ * original url => {@link #getOriginalRequestUrlParameterName()}
+ * </li>
+ * <li>
+ * If {@link #isIncludeOriginalParameters()}
+ * </li>
+ * <li>
+ * original method => {@link #getOriginalRequestMethodParameterName()}
+ * </li>
+ * <li>
+ * original parameters => {@link #getOriginalRequestParametersParameterName()}
+ * </li>
+ * <li>
+ * The original parameters string is contructed using :
+ * 
+ * <ul>
+ * <li>
+ * a parameter separator {@link #getOriginalRequestParametersSeparator()}
+ * </li>
+ * <li>
+ * a parameter name value pair separator for each parameter {@link
+ * #getOriginalRequestParametersNameValueSeparator()}
+ * </li>
+ * </ul>
+ * 
+ * </li>
+ * </ul>
+ * 
+ * <br><br>
+ * Default values :<br>
+ * forceHttps = false<br>
+ * includesOriginalRequest = true<br>
+ * includesOriginalParameters = false<br>
+ * isOutsideWebApp=false<br>
+ * originalRequestUrlParameterName  =original_requestUrl <br>
+ * originalRequestParametersParameterName = original_request_parameters<br>
+ * originalRequestParametersNameValueSeparator =   __ <br>
+ * originalRequestParametersSeparator =  ;; <br>
+ * originalRequestMethodParameterName =  original_request_method    <br>
+ * urlEncodingCharset = UTF-8<br>
  *
  * @author marc antoine Garrigue
  * @version $Id$
  */
 public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
+    //~ Static fields/initializers =============================================
+
     // ~ Static fields/initializers
     // =============================================
+    private static final Log logger = LogFactory.getLog(CaptchaEntryPoint.class);
 
-    private static final Log logger = LogFactory
-            .getLog(CaptchaEntryPoint.class);
+    //~ Instance fields ========================================================
 
     // ~ Instance fields
     // ========================================================
-
     private PortMapper portMapper = new PortMapperImpl();
-
     private PortResolver portResolver = new PortResolverImpl();
-
     private String captchaFormUrl;
-
-    private boolean forceHttps = false;
-
-    private String originalRequestUrlParameterName = "original_requestUrl";
-
+    private String originalRequestMethodParameterName = "original_request_method";
+    private String originalRequestParametersNameValueSeparator = "__";
     private String originalRequestParametersParameterName = "original_request_parameters";
-
-    private String originalRequestParametersNameValueSeparator = "@@";
-
     private String originalRequestParametersSeparator = ";;";
-
-    private String originalRequestMethodParameterName = "original_request_method";
-
+    private String originalRequestUrlParameterName = "original_requestUrl";
     private String urlEncodingCharset = "UTF-8";
-
-    private boolean isOutsideWebApp = false;
-
-    private boolean includeOriginalRequest = true;
-
+    private boolean forceHttps = false;
     private boolean includeOriginalParameters = false;
+    private boolean includeOriginalRequest = true;
+    private boolean isOutsideWebApp = false;
 
-    // ~ Methods
-    // ================================================================
-
-    /**
-     * Set to true to force captcha form access to be via https. If this value is ture (the default is false), and the
-     * incoming request for the protected resource which triggered the interceptor was not already <code>https</code>,
-     * then
-     */
-    public void setForceHttps(boolean forceHttps) {
-        this.forceHttps = forceHttps;
-    }
-
-    public boolean getForceHttps() {
-        return forceHttps;
-    }
+    //~ Methods ================================================================
 
     /**
-     * The URL where the <code>CaptchaProcessingFilter</code> login page can be found. Should be relative to the web-app
-     * context path, and include a leading <code>/</code>
+     * The URL where the <code>CaptchaProcessingFilter</code> login page can be
+     * found. Should be relative to the web-app context path, and include a
+     * leading <code>/</code>
+     *
+     * @param captchaFormUrl
      */
     public void setCaptchaFormUrl(String captchaFormUrl) {
         this.captchaFormUrl = captchaFormUrl;
     }
 
     /**
+     * DOCUMENT ME!
+     *
      * @return the captcha test page to redirect to.
      */
     public String getCaptchaFormUrl() {
         return captchaFormUrl;
     }
 
-    public void setPortMapper(PortMapper portMapper) {
-        this.portMapper = portMapper;
-    }
+    // ~ Methods
+    // ================================================================
 
-    public PortMapper getPortMapper() {
-        return portMapper;
+    /**
+     * Set to true to force captcha form access to be via https. If this value
+     * is ture (the default is false), and the incoming request for the
+     * protected resource which triggered the interceptor was not already
+     * <code>https</code>, then
+     *
+     * @param forceHttps
+     */
+    public void setForceHttps(boolean forceHttps) {
+        this.forceHttps = forceHttps;
     }
 
-    public void setPortResolver(PortResolver portResolver) {
-        this.portResolver = portResolver;
+    public boolean getForceHttps() {
+        return forceHttps;
     }
 
-    public PortResolver getPortResolver() {
-        return portResolver;
+    public void setIncludeOriginalParameters(boolean includeOriginalParameters) {
+        this.includeOriginalParameters = includeOriginalParameters;
     }
 
-
-    public boolean isOutsideWebApp() {
-        return isOutsideWebApp;
+    public boolean isIncludeOriginalParameters() {
+        return includeOriginalParameters;
     }
 
+    /**
+     * If set to true, the original request url will be appended to the
+     * redirect url using the {@link #getOriginalRequestUrlParameterName()}.
+     *
+     * @param includeOriginalRequest
+     */
+    public void setIncludeOriginalRequest(boolean includeOriginalRequest) {
+        this.includeOriginalRequest = includeOriginalRequest;
+    }
 
-    public String getOriginalRequestUrlParameterName() {
-        return originalRequestUrlParameterName;
+    public boolean isIncludeOriginalRequest() {
+        return includeOriginalRequest;
     }
 
-    public void setOriginalRequestUrlParameterName(String originalRequestUrlParameterName) {
-        this.originalRequestUrlParameterName = originalRequestUrlParameterName;
+    public void setOriginalRequestMethodParameterName(
+        String originalRequestMethodParameterName) {
+        this.originalRequestMethodParameterName = originalRequestMethodParameterName;
     }
 
-    public String getOriginalRequestParametersParameterName() {
-        return originalRequestParametersParameterName;
+    public String getOriginalRequestMethodParameterName() {
+        return originalRequestMethodParameterName;
     }
 
-    public void setOriginalRequestParametersParameterName(String originalRequestParametersParameterName) {
-        this.originalRequestParametersParameterName = originalRequestParametersParameterName;
+    public void setOriginalRequestParametersNameValueSeparator(
+        String originalRequestParametersNameValueSeparator) {
+        this.originalRequestParametersNameValueSeparator = originalRequestParametersNameValueSeparator;
     }
 
     public String getOriginalRequestParametersNameValueSeparator() {
         return originalRequestParametersNameValueSeparator;
     }
 
-    public void setOriginalRequestParametersNameValueSeparator(String originalRequestParametersNameValueSeparator) {
-        this.originalRequestParametersNameValueSeparator = originalRequestParametersNameValueSeparator;
+    public void setOriginalRequestParametersParameterName(
+        String originalRequestParametersParameterName) {
+        this.originalRequestParametersParameterName = originalRequestParametersParameterName;
     }
 
-    public String getOriginalRequestParametersSeparator() {
-        return originalRequestParametersSeparator;
+    public String getOriginalRequestParametersParameterName() {
+        return originalRequestParametersParameterName;
     }
 
-    public void setOriginalRequestParametersSeparator(String originalRequestParametersSeparator) {
+    public void setOriginalRequestParametersSeparator(
+        String originalRequestParametersSeparator) {
         this.originalRequestParametersSeparator = originalRequestParametersSeparator;
     }
 
-    public String getOriginalRequestMethodParameterName() {
-        return originalRequestMethodParameterName;
-    }
-
-    public void setOriginalRequestMethodParameterName(String originalRequestMethodParameterName) {
-        this.originalRequestMethodParameterName = originalRequestMethodParameterName;
+    public String getOriginalRequestParametersSeparator() {
+        return originalRequestParametersSeparator;
     }
 
-    public String getUrlEncodingCharset() {
-        return urlEncodingCharset;
+    public void setOriginalRequestUrlParameterName(
+        String originalRequestUrlParameterName) {
+        this.originalRequestUrlParameterName = originalRequestUrlParameterName;
     }
 
-    public void setUrlEncodingCharset(String urlEncodingCharset) {
-        this.urlEncodingCharset = urlEncodingCharset;
+    public String getOriginalRequestUrlParameterName() {
+        return originalRequestUrlParameterName;
     }
 
     /**
-     * if set to true, the {@link #commence(ServletRequest, ServletResponse)} method uses the {@link
-     * #getCaptchaFormUrl()} as a complete URL, else it as a 'inside WebApp' path.
+     * if set to true, the {@link #commence(ServletRequest, ServletResponse)}
+     * method uses the {@link #getCaptchaFormUrl()} as a complete URL, else it
+     * as a 'inside WebApp' path.
+     *
+     * @param isOutsideWebApp
      */
     public void setOutsideWebApp(boolean isOutsideWebApp) {
         this.isOutsideWebApp = isOutsideWebApp;
     }
 
+    public boolean isOutsideWebApp() {
+        return isOutsideWebApp;
+    }
 
-    public boolean isIncludeOriginalRequest() {
-        return includeOriginalRequest;
+    public void setPortMapper(PortMapper portMapper) {
+        this.portMapper = portMapper;
     }
 
-    /**
-     * If set to true, the original request url will be appended to the redirect url using the {@link
-     * #getOriginalRequestParameterName()}.
-     */
-    public void setIncludeOriginalRequest(boolean includeOriginalRequest) {
-        this.includeOriginalRequest = includeOriginalRequest;
+    public PortMapper getPortMapper() {
+        return portMapper;
     }
 
-    public boolean isIncludeOriginalParameters() {
-        return includeOriginalParameters;
+    public void setPortResolver(PortResolver portResolver) {
+        this.portResolver = portResolver;
     }
 
-    public void setIncludeOriginalParameters(boolean includeOriginalParameters) {
-        this.includeOriginalParameters = includeOriginalParameters;
+    public PortResolver getPortResolver() {
+        return portResolver;
+    }
+
+    public void setUrlEncodingCharset(String urlEncodingCharset) {
+        this.urlEncodingCharset = urlEncodingCharset;
+    }
+
+    public String getUrlEncodingCharset() {
+        return urlEncodingCharset;
     }
 
     public void afterPropertiesSet() throws Exception {
         Assert.hasLength(captchaFormUrl, "captchaFormUrl must be specified");
-        Assert.hasLength(originalRequestMethodParameterName, "originalRequestMethodParameterName must be specified");
-        Assert.hasLength(originalRequestParametersNameValueSeparator, "originalRequestParametersNameValueSeparator must be specified");
-        Assert.hasLength(originalRequestParametersParameterName, "originalRequestParametersParameterName must be specified");
-        Assert.hasLength(originalRequestParametersSeparator, "originalRequestParametersSeparator must be specified");
-        Assert.hasLength(originalRequestUrlParameterName, "originalRequestUrlParameterName must be specified");
-        Assert.hasLength(urlEncodingCharset, "urlEncodingCharset must be specified");
+        Assert.hasLength(originalRequestMethodParameterName,
+            "originalRequestMethodParameterName must be specified");
+        Assert.hasLength(originalRequestParametersNameValueSeparator,
+            "originalRequestParametersNameValueSeparator must be specified");
+        Assert.hasLength(originalRequestParametersParameterName,
+            "originalRequestParametersParameterName must be specified");
+        Assert.hasLength(originalRequestParametersSeparator,
+            "originalRequestParametersSeparator must be specified");
+        Assert.hasLength(originalRequestUrlParameterName,
+            "originalRequestUrlParameterName must be specified");
+        Assert.hasLength(urlEncodingCharset,
+            "urlEncodingCharset must be specified");
         Assert.notNull(portMapper, "portMapper must be specified");
         Assert.notNull(portResolver, "portResolver must be specified");
         URLEncoder.encode("   fzaef é& à ", urlEncodingCharset);
     }
 
     public void commence(ServletRequest request, ServletResponse response)
-            throws IOException, ServletException {
+        throws IOException, ServletException {
         StringBuffer redirectUrl = new StringBuffer();
         HttpServletRequest req = (HttpServletRequest) request;
 
@@ -259,70 +304,17 @@ public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
         if (includeOriginalRequest) {
             includeOriginalRequest(redirectUrl, req);
         }
+
         // add post parameter? DONE!
         if (logger.isDebugEnabled()) {
             logger.debug("Redirecting to: " + redirectUrl);
         }
 
-        ((HttpServletResponse) response)
-                .sendRedirect(redirectUrl.toString());
-    }
-
-    private void includeOriginalRequest(StringBuffer redirectUrl,
-                                        HttpServletRequest req) {
-        // add original request to the url
-        if (redirectUrl.indexOf("?") >= 0) {
-            redirectUrl.append("&");
-        } else {
-            redirectUrl.append("?");
-        }
-
-        redirectUrl.append(originalRequestUrlParameterName);
-        redirectUrl.append("=");
-        try {
-            redirectUrl.append(URLEncoder.encode(req.getRequestURL().toString(), urlEncodingCharset));
-        } catch (UnsupportedEncodingException e) {
-            logger.warn(e);
-        }
-
-        //append method
-        redirectUrl.append("&");
-        redirectUrl.append(originalRequestMethodParameterName);
-        redirectUrl.append("=");
-        redirectUrl.append(req.getMethod());
-        if (includeOriginalParameters) {
-
-            // append query params
-
-            redirectUrl.append("&");
-            redirectUrl.append(originalRequestParametersParameterName);
-            redirectUrl.append("=");
-            StringBuffer qp = new StringBuffer();
-            Enumeration parameters = req.getParameterNames();
-            if (parameters != null && parameters.hasMoreElements()) {
-                //qp.append("?");
-                while (parameters.hasMoreElements()) {
-                    String name = parameters.nextElement().toString();
-                    String value = req.getParameter(name);
-                    qp.append(name);
-                    qp.append(originalRequestParametersNameValueSeparator);
-                    qp.append(value);
-                    if (parameters.hasMoreElements()) {
-                        qp.append(originalRequestParametersSeparator);
-                    }
-                }
-            }
-            try {
-                redirectUrl.append(URLEncoder.encode(qp.toString(), urlEncodingCharset));
-            } catch (Exception e) {
-                logger.warn(e);
-            }
-        }
-
+        ((HttpServletResponse) response).sendRedirect(redirectUrl.toString());
     }
 
     private void buildInternalRedirect(StringBuffer redirectUrl,
-                                       HttpServletRequest req) {
+        HttpServletRequest req) {
         // construct it
         StringBuffer simpleRedirect = new StringBuffer();
 
@@ -331,9 +323,11 @@ public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
         int serverPort = portResolver.getServerPort(req);
         String contextPath = req.getContextPath();
         boolean includePort = true;
+
         if ("http".equals(scheme.toLowerCase()) && (serverPort == 80)) {
             includePort = false;
         }
+
         if ("https".equals(scheme.toLowerCase()) && (serverPort == 443)) {
             includePort = false;
         }
@@ -341,10 +335,12 @@ public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
         simpleRedirect.append(scheme);
         simpleRedirect.append("://");
         simpleRedirect.append(serverName);
+
         if (includePort) {
             simpleRedirect.append(":");
             simpleRedirect.append(serverPort);
         }
+
         simpleRedirect.append(contextPath);
         simpleRedirect.append(captchaFormUrl);
 
@@ -361,10 +357,12 @@ public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
 
                 redirectUrl.append("https://");
                 redirectUrl.append(serverName);
+
                 if (includePort) {
                     redirectUrl.append(":");
                     redirectUrl.append(httpsPort);
                 }
+
                 redirectUrl.append(contextPath);
                 redirectUrl.append(captchaFormUrl);
             } else {
@@ -375,4 +373,61 @@ public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
         }
     }
 
+    private void includeOriginalRequest(StringBuffer redirectUrl,
+        HttpServletRequest req) {
+        // add original request to the url
+        if (redirectUrl.indexOf("?") >= 0) {
+            redirectUrl.append("&");
+        } else {
+            redirectUrl.append("?");
+        }
+
+        redirectUrl.append(originalRequestUrlParameterName);
+        redirectUrl.append("=");
+
+        try {
+            redirectUrl.append(URLEncoder.encode(req.getRequestURL().toString(),
+                    urlEncodingCharset));
+        } catch (UnsupportedEncodingException e) {
+            logger.warn(e);
+        }
+
+        //append method
+        redirectUrl.append("&");
+        redirectUrl.append(originalRequestMethodParameterName);
+        redirectUrl.append("=");
+        redirectUrl.append(req.getMethod());
+
+        if (includeOriginalParameters) {
+            // append query params
+            redirectUrl.append("&");
+            redirectUrl.append(originalRequestParametersParameterName);
+            redirectUrl.append("=");
+
+            StringBuffer qp = new StringBuffer();
+            Enumeration parameters = req.getParameterNames();
+
+            if ((parameters != null) && parameters.hasMoreElements()) {
+                //qp.append("?");
+                while (parameters.hasMoreElements()) {
+                    String name = parameters.nextElement().toString();
+                    String value = req.getParameter(name);
+                    qp.append(name);
+                    qp.append(originalRequestParametersNameValueSeparator);
+                    qp.append(value);
+
+                    if (parameters.hasMoreElements()) {
+                        qp.append(originalRequestParametersSeparator);
+                    }
+                }
+            }
+
+            try {
+                redirectUrl.append(URLEncoder.encode(qp.toString(),
+                        urlEncodingCharset));
+            } catch (Exception e) {
+                logger.warn(e);
+            }
+        }
+    }
 }

+ 36 - 28
core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContext.java

@@ -12,42 +12,50 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package net.sf.acegisecurity.captcha;
 
 import net.sf.acegisecurity.context.SecurityContext;
 
+
 /**
  * Interface that add humanity concerns to the SecurityContext
- * 
+ *
  * @author marc antoine garrigue
  */
 public interface CaptchaSecurityContext extends SecurityContext {
+    //~ Methods ================================================================
+
+    /**
+     * set human attribute, should called after captcha validation.
+     */
+    void setHuman();
+
+    /**
+     * DOCUMENT ME!
+     *
+     * @return true if the current user has already passed a captcha.
+     */
+    boolean isHuman();
+
+    /**
+     * DOCUMENT ME!
+     *
+     * @return number of human restricted resources requests since the last
+     *         passed captcha.
+     */
+    int getHumanRestrictedResourcesRequestsCount();
+
+    /**
+     * DOCUMENT ME!
+     *
+     * @return the date of the last passed Captcha in millis, 0 if the user
+     *         never passed captcha.
+     */
+    long getLastPassedCaptchaDateInMillis();
 
-	/**
-	 * @return true if the current user has already passed a captcha.
-	 */
-	boolean isHuman();
-
-	/**
-	 * set human attribute, should called after captcha validation.
-	 */
-	void setHuman();
-
-	/**
-	 *
-	 * @return number of human restricted resources requests since the last
-	 *         passed captcha.
-	 */
-	int getHumanRestrictedResourcesRequestsCount();
-
-	/**
-	 * @return the date of the last passed Captcha in millis, 0 if the user
-	 *         never passed captcha.
-	 */
-	long getLastPassedCaptchaDateInMillis();
-
-	/**
-	 * Method to increment the human Restricted Resrouces Requests Count;
-	 */
-	void incrementHumanRestrictedRessoucesRequestsCount();
+    /**
+     * Method to increment the human Restricted Resrouces Requests Count;
+     */
+    void incrementHumanRestrictedRessoucesRequestsCount();
 }

+ 60 - 55
core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContextImpl.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -12,74 +12,79 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package net.sf.acegisecurity.captcha;
 
 import net.sf.acegisecurity.context.SecurityContextImpl;
 
+
 /**
+ * Default CaptchaSecurityContext implementation
+ *
  * @author mag
- * 
  */
-public class CaptchaSecurityContextImpl extends SecurityContextImpl implements
-		CaptchaSecurityContext {
-
-	private boolean human;
+public class CaptchaSecurityContextImpl extends SecurityContextImpl
+    implements CaptchaSecurityContext {
+    //~ Instance fields ========================================================
 
-	private long lastPassedCaptchaDate;
+    private boolean human;
+    private int humanRestrictedResourcesRequestsCount;
+    private long lastPassedCaptchaDate;
 
-	private int humanRestrictedResourcesRequestsCount;
+    //~ Constructors ===========================================================
 
-	/**
-	 * 
-	 */
-	public CaptchaSecurityContextImpl() {
-		super();
-		human = false;
-		lastPassedCaptchaDate = 0;
-		humanRestrictedResourcesRequestsCount = 0;
-	}
+    /**
+     *
+     */
+    public CaptchaSecurityContextImpl() {
+        super();
+        human = false;
+        lastPassedCaptchaDate = 0;
+        humanRestrictedResourcesRequestsCount = 0;
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * 
-	 * @see net.sf.acegisecurity.context.CaptchaSecurityContext#isHuman()
-	 */
-	public boolean isHuman() {
-		return human;
-	}
+    //~ Methods ================================================================
 
-	/**
-	 * reset the lastPassedCaptchaDate and count.
-	 */
-	public void setHuman() {
-		this.human = true;
-		this.lastPassedCaptchaDate = System.currentTimeMillis();
-		this.humanRestrictedResourcesRequestsCount = 0;
-	}
+    /**
+     * reset the lastPassedCaptchaDate and count.
+     */
+    public void setHuman() {
+        this.human = true;
+        this.lastPassedCaptchaDate = System.currentTimeMillis();
+        this.humanRestrictedResourcesRequestsCount = 0;
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * 
-	 * @see net.sf.acegisecurity.context.CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()
-	 */
-	public int getHumanRestrictedResourcesRequestsCount() {
-		return humanRestrictedResourcesRequestsCount;
-	}
+    /*
+     * (non-Javadoc)
+     *
+     * @see net.sf.acegisecurity.context.CaptchaSecurityContext#isHuman()
+     */
+    public boolean isHuman() {
+        return human;
+    }
 
-	/*
-	 * (non-Javadoc)
-	 * 
-	 * @see net.sf.acegisecurity.context.CaptchaSecurityContext#getLastPassedCaptchaDateInMillis()
-	 */
-	public long getLastPassedCaptchaDateInMillis() {
+    /*
+     * (non-Javadoc)
+     *
+     * @see net.sf.acegisecurity.context.CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()
+     */
+    public int getHumanRestrictedResourcesRequestsCount() {
+        return humanRestrictedResourcesRequestsCount;
+    }
 
-		return lastPassedCaptchaDate;
-	}
+    /*
+     * (non-Javadoc)
+     *
+     * @see net.sf.acegisecurity.context.CaptchaSecurityContext#getLastPassedCaptchaDateInMillis()
+     */
+    public long getLastPassedCaptchaDateInMillis() {
+        return lastPassedCaptchaDate;
+    }
 
-	/**
-	 * Method to increment the human Restricted Resrouces Requests Count;
-	 */
-	public void incrementHumanRestrictedRessoucesRequestsCount() {
-		humanRestrictedResourcesRequestsCount++;
-	};
+    /**
+     * Method to increment the human Restricted Resrouces Requests Count;
+     */
+    public void incrementHumanRestrictedRessoucesRequestsCount() {
+        humanRestrictedResourcesRequestsCount++;
+    }
 }

+ 12 - 6
core/src/main/java/org/acegisecurity/captcha/CaptchaServiceProxy.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -17,18 +17,24 @@ package net.sf.acegisecurity.captcha;
 
 import javax.servlet.ServletRequest;
 
+
 /**
  * Provide a common interface for captcha validation.
- * 
+ *
  * @author marc antoine Garrigue
  * @version $Id$
  */
 public interface CaptchaServiceProxy {
+    //~ Methods ================================================================
 
-	/**
+    /**
+     * DOCUMENT ME!
+     *
      * @param id the id token
      * @param captchaResponse the user response
-	 * @return true if the response is validated by the back end captcha service.
-	 */
-	boolean validateReponseForId(String id , Object captchaResponse);
+     *
+     * @return true if the response is validated by the back end captcha
+     *         service.
+     */
+    boolean validateReponseForId(String id, Object captchaResponse);
 }

+ 57 - 44
core/src/main/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilter.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -15,87 +15,96 @@
 
 package net.sf.acegisecurity.captcha;
 
-import net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter;
 import net.sf.acegisecurity.context.SecurityContextHolder;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+
 import org.springframework.beans.factory.InitializingBean;
 
+import java.io.IOException;
+
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
-import java.io.IOException;
+
 
 /**
- * Filter for web integration of the {@link CaptchaServiceProxy}. <br/> It basically intercept calls containing the
- * specific validation parameter, use the {@link CaptchaServiceProxy} to validate the request, and update the {@link
- * CaptchaSecurityContext} if the request passed the validation. <br/> <br/> This Filter should be placed after the
- * ContextIntegration filter and before the {@link CaptchaChannelProcessor} filter in the filter stack in order to
- * update the {@link CaptchaSecurityContext} before the humanity verification routine occurs. <br/> <br/> This filter
- * should only be used in conjunction with the {@link CaptchaSecurityContext} <br/> <br/>
+ * Filter for web integration of the {@link CaptchaServiceProxy}. <br>
+ * It basically intercept calls containing the specific validation parameter,
+ * use the {@link CaptchaServiceProxy} to validate the request, and update the
+ * {@link CaptchaSecurityContext} if the request passed the validation. <br>
+ * This Filter should be placed after the ContextIntegration filter and before
+ * the {@link CaptchaChannelProcessorTemplate} filter in the filter stack in
+ * order to update the {@link CaptchaSecurityContext} before the humanity
+ * verification routine occurs. <br>
+ * This filter should only be used in conjunction with the {@link
+ * CaptchaSecurityContext}<br>
  *
  * @author marc antoine Garrigue
  * @version $Id$
  */
 public class CaptchaValidationProcessingFilter implements InitializingBean,
-        Filter {
+    Filter {
+    //~ Static fields/initializers =============================================
+
     // ~ Static fields/initializers
     // =============================================
-    protected static final Log logger = LogFactory
-            .getLog(CaptchaValidationProcessingFilter.class);
+    protected static final Log logger = LogFactory.getLog(CaptchaValidationProcessingFilter.class);
+
+    //~ Instance fields ========================================================
 
     // ~ Instance fields
     // ========================================================
-
     private CaptchaServiceProxy captchaService;
     private String captchaValidationParameter = "_captcha_parameter";
 
+    //~ Methods ================================================================
+
+    public void setCaptchaService(CaptchaServiceProxy captchaService) {
+        this.captchaService = captchaService;
+    }
 
     // ~ Methods
     // ================================================================
-
     public CaptchaServiceProxy getCaptchaService() {
         return captchaService;
     }
 
-    public void setCaptchaService(CaptchaServiceProxy captchaService) {
-        this.captchaService = captchaService;
+    public void setCaptchaValidationParameter(String captchaValidationParameter) {
+        this.captchaValidationParameter = captchaValidationParameter;
     }
 
-
     public String getCaptchaValidationParameter() {
         return captchaValidationParameter;
     }
 
-    public void setCaptchaValidationParameter(String captchaValidationParameter) {
-        this.captchaValidationParameter = captchaValidationParameter;
-    }
-
     public void afterPropertiesSet() throws Exception {
         if (this.captchaService == null) {
             throw new IllegalArgumentException(
-                    "CaptchaServiceProxy must be defined ");
+                "CaptchaServiceProxy must be defined ");
         }
-        if (this.captchaValidationParameter == null||"".equals(captchaValidationParameter)) {
+
+        if ((this.captchaValidationParameter == null)
+            || "".equals(captchaValidationParameter)) {
             throw new IllegalArgumentException(
-                    "captchaValidationParameter must not be empty or null");
+                "captchaValidationParameter must not be empty or null");
         }
     }
 
     /**
      * Does nothing. We use IoC container lifecycle services instead.
      */
-    public void destroy() {
-    }
+    public void destroy() {}
 
     public void doFilter(ServletRequest request, ServletResponse response,
-                         FilterChain chain) throws IOException, ServletException {
-        String captcha_reponse = request
-                .getParameter(captchaValidationParameter);
-        if ((request != null) && request instanceof HttpServletRequest
-                && ( captcha_reponse!= null)) {
+        FilterChain chain) throws IOException, ServletException {
+        String captcha_reponse = request.getParameter(captchaValidationParameter);
 
+        if ((request != null) && request instanceof HttpServletRequest
+            && (captcha_reponse != null)) {
             logger.debug("captcha validation parameter found");
+
             // validate the request against CaptchaServiceProxy
             boolean valid = false;
 
@@ -103,30 +112,35 @@ public class CaptchaValidationProcessingFilter implements InitializingBean,
 
             //get session
             HttpSession session = ((HttpServletRequest) request).getSession();
-            if (session != null) {
 
+            if (session != null) {
                 String id = session.getId();
                 valid = this.captchaService.validateReponseForId(id,
                         captcha_reponse);
                 logger.debug("captchaServiceProxy says : request is valid = "
-                        + valid);
+                    + valid);
+
                 if (valid) {
                     logger.debug("update the context");
                     ((CaptchaSecurityContext) SecurityContextHolder.getContext())
-                            .setHuman();
-                    //logger.debug("retrieve original request from ")
+                    .setHuman();
 
-                }else{
-                  logger.debug("captcha test failed");
+                    //logger.debug("retrieve original request from ")
+                } else {
+                    logger.debug("captcha test failed");
                 }
-
-            }else{
-                logger.debug("no session found, user don't even ask a captcha challenge");
+            } else {
+                logger.debug(
+                    "no session found, user don't even ask a captcha challenge");
             }
         } else {
-           logger.debug("captcha validation parameter not found, do nothing");
+            logger.debug("captcha validation parameter not found, do nothing");
         }
-        logger.debug("chain ...");
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("chain ...");
+        }
+
         chain.doFilter(request, response);
     }
 
@@ -137,6 +151,5 @@ public class CaptchaValidationProcessingFilter implements InitializingBean,
      *
      * @throws ServletException ignored
      */
-    public void init(FilterConfig filterConfig) throws ServletException {
-    }
+    public void init(FilterConfig filterConfig) throws ServletException {}
 }

+ 56 - 0
core/src/main/java/org/acegisecurity/captcha/TestOnceAfterMaxRequestsCaptchaChannelProcessor.java

@@ -0,0 +1,56 @@
+/* Copyright 2004, 2005 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.captcha;
+
+/**
+ * <p>
+ * return false if ny CaptchaChannelProcessorTemplate mapped urls has been
+ * requested more than thresold and humanity is false; <br>
+ * Default keyword : REQUIRES_CAPTCHA_ONCE_ABOVE_THRESOLD_REQUESTS
+ * </p>
+ *
+ * @author Marc-Antoine Garrigue
+ * @version $Id$
+ */
+public class TestOnceAfterMaxRequestsCaptchaChannelProcessor
+    extends CaptchaChannelProcessorTemplate {
+    //~ Static fields/initializers =============================================
+
+    public static final String DEFAULT_KEYWORD = "REQUIRES_CAPTCHA_ONCE_ABOVE_THRESOLD_REQUESTS";
+
+    //~ Constructors ===========================================================
+
+    public TestOnceAfterMaxRequestsCaptchaChannelProcessor() {
+        super();
+        this.setKeyword(DEFAULT_KEYWORD);
+    }
+
+    //~ Methods ================================================================
+
+    boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
+        if (context.isHuman()
+            || (context.getHumanRestrictedResourcesRequestsCount() < getThresold())) {
+            logger.debug(
+                "context is valid concerning humanity or request count < thresold");
+
+            return true;
+        } else {
+            logger.debug(
+                "context is not valid concerning humanity and request count > thresold");
+
+            return false;
+        }
+    }
+}

+ 7 - 1
core/src/main/java/org/acegisecurity/captcha/package.html

@@ -1,6 +1,12 @@
 <html>
 <body>
-Captcha classes.
+Captcha classes. Contains :<br/>
+<ul>
+    <li>a CaptchaSecurityContext that overrides the default SecurityContext and holds some captcha related informations</li>
+    <li>an abstract CaptchaChannelProcessorTemplate and its implementations that test this context according to the configuration</li>
+    <li>a CaptchaServiceProxy and a CaptchaValidationProcessingFilter that alows to validate a captcha response and to update the CaptchaSecurity</li>
+    <li>a CaptchaEntryPoint that redirects to a captcha page if the CaptchaChannelProcessor implementation decide so</li>
+</ul>
 </body>
 </html>
 

+ 76 - 0
core/src/test/java/org/acegisecurity/captcha/AlwaysTestAfterMaxRequestsCaptchaChannelProcessorTests.java

@@ -0,0 +1,76 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import junit.framework.*;
+
+import net.sf.acegisecurity.captcha.AlwaysTestAfterMaxRequestsCaptchaChannelProcessor;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision$
+ */
+public class AlwaysTestAfterMaxRequestsCaptchaChannelProcessorTests
+    extends TestCase {
+    //~ Instance fields ========================================================
+
+    AlwaysTestAfterMaxRequestsCaptchaChannelProcessor alwaysTestAfterMaxRequestsCaptchaChannelProcessor;
+
+    //~ Methods ================================================================
+
+    public void testIsContextValidConcerningHumanity()
+        throws Exception {
+        alwaysTestAfterMaxRequestsCaptchaChannelProcessor.setThresold(1);
+
+        CaptchaSecurityContextImpl context = new CaptchaSecurityContextImpl();
+        assertTrue(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+
+        alwaysTestAfterMaxRequestsCaptchaChannelProcessor.setThresold(-1);
+        assertFalse(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        alwaysTestAfterMaxRequestsCaptchaChannelProcessor.setThresold(3);
+        assertTrue(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertTrue(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertFalse(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    public void testNewContext() {
+        CaptchaSecurityContextImpl context = new CaptchaSecurityContextImpl();
+
+        assertFalse(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        alwaysTestAfterMaxRequestsCaptchaChannelProcessor.setThresold(1);
+        assertTrue(alwaysTestAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        alwaysTestAfterMaxRequestsCaptchaChannelProcessor = new AlwaysTestAfterMaxRequestsCaptchaChannelProcessor();
+    }
+}

+ 98 - 0
core/src/test/java/org/acegisecurity/captcha/AlwaysTestAfterTimeInMillisCaptchaChannelProcessorTests.java

@@ -0,0 +1,98 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import junit.framework.*;
+
+import net.sf.acegisecurity.captcha.AlwaysTestAfterTimeInMillisCaptchaChannelProcessor;
+
+
+/**
+ * WARNING! This test class make some assumptions concerning the compute speed!
+ * For example the two following instructions should be computed in the same
+ * millis or the test is not valid.
+ * <pre><code>
+ * context.setHuman();
+ * assertFalse(alwaysTestAfterTimeInMillisCaptchaChannelProcessor.isContextValidConcerningHumanity(context));
+ * </code></pre>
+ * This should be the case for most environements unless
+ * 
+ * <ul>
+ * <li>
+ * you run it on a good old TRS-80
+ * </li>
+ * <li>
+ * you start M$office during this test ;)
+ * </li>
+ * </ul>
+ */
+public class AlwaysTestAfterTimeInMillisCaptchaChannelProcessorTests
+    extends TestCase {
+    //~ Instance fields ========================================================
+
+    AlwaysTestAfterTimeInMillisCaptchaChannelProcessor alwaysTestAfterTimeInMillisCaptchaChannelProcessor;
+
+    //~ Methods ================================================================
+
+    public void testEqualsThresold() {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        assertFalse(alwaysTestAfterTimeInMillisCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        //the two following instructions should be computed or the test is not valid (never fails). This should be the case
+        // for most environements unless if you run it on a good old TRS-80 (thanks mom).
+        context.setHuman();
+        assertFalse(alwaysTestAfterTimeInMillisCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    public void testIsContextValidConcerningHumanity()
+        throws Exception {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        alwaysTestAfterTimeInMillisCaptchaChannelProcessor.setThresold(100);
+        context.setHuman();
+
+        while ((System.currentTimeMillis()
+            - context.getLastPassedCaptchaDateInMillis()) < alwaysTestAfterTimeInMillisCaptchaChannelProcessor
+            .getThresold()) {
+            assertTrue(alwaysTestAfterTimeInMillisCaptchaChannelProcessor
+                .isContextValidConcerningHumanity(context));
+            context.incrementHumanRestrictedRessoucesRequestsCount();
+
+            long now = System.currentTimeMillis();
+
+            while ((System.currentTimeMillis() - now) < 1) {}
+
+            ;
+        }
+
+        assertFalse(alwaysTestAfterTimeInMillisCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    public void testNewContext() {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+
+        //alwaysTestAfterTimeInMillisCaptchaChannelProcessor.setThresold(10);
+        assertFalse(alwaysTestAfterTimeInMillisCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        alwaysTestAfterTimeInMillisCaptchaChannelProcessor = new AlwaysTestAfterTimeInMillisCaptchaChannelProcessor();
+    }
+}

+ 125 - 0
core/src/test/java/org/acegisecurity/captcha/AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessorTests.java

@@ -0,0 +1,125 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import junit.framework.TestCase;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision$
+ */
+public class AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessorTests
+    extends TestCase {
+    //~ Instance fields ========================================================
+
+    AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor;
+
+    //~ Methods ================================================================
+
+    public void testEqualsThresold() {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+        .setThresold(100);
+
+        context.setHuman();
+
+        long now = System.currentTimeMillis();
+
+        while ((System.currentTimeMillis() - now) <= 100) {
+            assertTrue(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+                .isContextValidConcerningHumanity(context));
+        }
+
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertTrue(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        context.setHuman();
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertFalse(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+        .setThresold(0);
+        context.setHuman();
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertFalse(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+        .setThresold(0);
+    }
+
+    public void testIsContextValidConcerningHumanity()
+        throws Exception {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+        .setThresold(10);
+        context.setHuman();
+
+        while ((System.currentTimeMillis()
+            - context.getLastPassedCaptchaDateInMillis()) < (10 * alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .getThresold())) {
+            assertTrue(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+                .isContextValidConcerningHumanity(context));
+        }
+    }
+
+    public void testNewContext() {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        assertFalse(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        context.setHuman();
+        assertTrue(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    public void testShouldPassAbove() {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+
+        context.setHuman();
+
+        int i = 0;
+
+        while ((System.currentTimeMillis()
+            - context.getLastPassedCaptchaDateInMillis()) < (100 * alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+            .getThresold())) {
+            System.out.println((System.currentTimeMillis()
+                - context.getLastPassedCaptchaDateInMillis()));
+
+            context.incrementHumanRestrictedRessoucesRequestsCount();
+            i++;
+
+            while ((System.currentTimeMillis()
+                - context.getLastPassedCaptchaDateInMillis()) < (alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+                .getThresold() * i)) {}
+
+            System.out.println((System.currentTimeMillis()
+                - context.getLastPassedCaptchaDateInMillis()));
+
+            assertTrue(alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor
+                .isContextValidConcerningHumanity(context));
+        }
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        alwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor = new AlwaysTestBelowAverageTimeInMillisBetweenRequestsChannelProcessor();
+    }
+}

+ 216 - 0
core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTemplateTests.java

@@ -0,0 +1,216 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.SecurityConfig;
+import net.sf.acegisecurity.context.SecurityContextHolder;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+
+/**
+ * Tests {@link net.sf.acegisecurity.captcha.CaptchaChannelProcessorTemplate}
+ *
+ * @author marc antoine Garrigue
+ * @version $Id$
+ */
+public class CaptchaChannelProcessorTemplateTests extends TestCase {
+    //~ Methods ================================================================
+
+    public void testContextRedirect() throws Exception {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        processor.setKeyword("X");
+
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("Y"));
+
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        SecurityContextHolder.setContext(context);
+
+        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
+        epoint.setCaptchaFormUrl("/jcaptcha.do");
+        epoint.setIncludeOriginalRequest(false);
+
+        processor.setEntryPoint(epoint);
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setQueryString("info=true");
+        request.setServerName("localhost");
+        request.setContextPath("/demo");
+        request.setServletPath("/restricted");
+        request.setScheme("http");
+        request.setServerPort(8000);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        processor.decide(fi, cad);
+        assertEquals(null, response.getRedirectedUrl());
+        processor.setKeyword("Y");
+        response = decideWithNewResponse(cad, processor, request);
+        assertEquals("http://localhost:8000/demo/jcaptcha.do",
+            response.getRedirectedUrl());
+        context.setHuman();
+        response = decideWithNewResponse(cad, processor, request);
+        assertEquals(null, response.getRedirectedUrl());
+    }
+
+    public void testDecideRejectsNulls() throws Exception {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        processor.setEntryPoint(new CaptchaEntryPoint());
+        processor.setKeyword("X");
+        processor.afterPropertiesSet();
+
+        try {
+            processor.decide(null, null);
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGettersSetters() {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        assertEquals(null, processor.getKeyword());
+        processor.setKeyword("X");
+        assertEquals("X", processor.getKeyword());
+
+        assertEquals(0, processor.getThresold());
+        processor.setThresold(1);
+        assertEquals(1, processor.getThresold());
+
+        assertTrue(processor.getEntryPoint() == null);
+        processor.setEntryPoint(new CaptchaEntryPoint());
+        assertTrue(processor.getEntryPoint() != null);
+    }
+
+    public void testIncrementRequestCount() throws Exception {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        processor.setKeyword("X");
+
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
+        cad.addConfigAttribute(new SecurityConfig("X"));
+
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        SecurityContextHolder.setContext(context);
+
+        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
+        epoint.setCaptchaFormUrl("/jcaptcha.do");
+        processor.setEntryPoint(epoint);
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setQueryString("info=true");
+        request.setServerName("localhost");
+        request.setContextPath("/demo");
+        request.setServletPath("/restricted");
+        request.setScheme("http");
+        request.setServerPort(8000);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain();
+        FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+        processor.decide(fi, cad);
+        assertEquals(0, context.getHumanRestrictedResourcesRequestsCount());
+        context.setHuman();
+        decideWithNewResponse(cad, processor, request);
+        assertEquals(1, context.getHumanRestrictedResourcesRequestsCount());
+        decideWithNewResponse(cad, processor, request);
+        assertEquals(2, context.getHumanRestrictedResourcesRequestsCount());
+        processor.setKeyword("Y");
+        decideWithNewResponse(cad, processor, request);
+        assertEquals(2, context.getHumanRestrictedResourcesRequestsCount());
+        context = new CaptchaSecurityContextImpl();
+        decideWithNewResponse(cad, processor, request);
+        assertEquals(0, context.getHumanRestrictedResourcesRequestsCount());
+    }
+
+    public void testMissingEntryPoint() throws Exception {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        processor.setEntryPoint(null);
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("entryPoint required", expected.getMessage());
+        }
+    }
+
+    public void testMissingKeyword() throws Exception {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        processor.setKeyword(null);
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+
+        processor.setKeyword("");
+
+        try {
+            processor.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {}
+    }
+
+    public void testSupports() {
+        CaptchaChannelProcessorTemplate processor = new TestHumanityCaptchaChannelProcessor();
+        processor.setKeyword("X");
+        assertTrue(processor.supports(
+                new SecurityConfig(processor.getKeyword())));
+
+        assertTrue(processor.supports(new SecurityConfig("X")));
+
+        assertFalse(processor.supports(null));
+
+        assertFalse(processor.supports(new SecurityConfig("NOT_SUPPORTED")));
+    }
+
+    private MockHttpServletResponse decideWithNewResponse(
+        ConfigAttributeDefinition cad,
+        CaptchaChannelProcessorTemplate processor,
+        MockHttpServletRequest request) throws IOException, ServletException {
+        MockHttpServletResponse response;
+        MockFilterChain chain;
+        FilterInvocation fi;
+        response = new MockHttpServletResponse();
+        chain = new MockFilterChain();
+        fi = new FilterInvocation(request, response, chain);
+        processor.decide(fi, cad);
+
+        return response;
+    }
+
+    //~ Inner Classes ==========================================================
+
+    private class TestHumanityCaptchaChannelProcessor
+        extends CaptchaChannelProcessorTemplate {
+        boolean isContextValidConcerningHumanity(CaptchaSecurityContext context) {
+            return context.isHuman();
+        }
+    }
+}

+ 0 - 552
core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTests.java

@@ -1,552 +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.captcha;
-
-import junit.framework.TestCase;
-import net.sf.acegisecurity.ConfigAttributeDefinition;
-import net.sf.acegisecurity.MockFilterChain;
-import net.sf.acegisecurity.SecurityConfig;
-import net.sf.acegisecurity.context.SecurityContextHolder;
-import net.sf.acegisecurity.intercept.web.FilterInvocation;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-
-import javax.servlet.ServletException;
-import java.io.IOException;
-
-/**
- * Tests {@link CaptchaChannelProcessor}
- *
- * @author marc antoine Garrigue
- * @version $Id$
- */
-public class CaptchaChannelProcessorTests extends TestCase {
-
-    public void testDecideRequestsFirstTestRequests() throws Exception {
-        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
-        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
-        cad.addConfigAttribute(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_REQUESTS"));
-
-        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
-        SecurityContextHolder.setContext(context);
-
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
-        epoint.setCaptchaFormUrl("/jcaptcha.do");
-        epoint.setIncludeOriginalRequest(false);
-
-        processor.setEntryPoint(epoint);
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setQueryString("info=true");
-        request.setServerName("localhost");
-        request.setContextPath("/demo");
-        request.setServletPath("/restricted");
-        request.setScheme("http");
-        request.setServerPort(8000);
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain();
-        FilterInvocation fi = new FilterInvocation(request, response, chain);
-
-        processor.decide(fi, cad);
-        assertEquals(response.getRedirectedUrl(),
-                "http://localhost:8000/demo/jcaptcha.do");
-
-        processor.setMaxRequestsBeforeFirstTest(1);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(response.getRedirectedUrl(), null);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(response.getRedirectedUrl(),
-                "http://localhost:8000/demo/jcaptcha.do");
-
-        processor.setMaxRequestsBeforeFirstTest(2);
-        processor.setMaxMillisBeforeReTest(0);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-    }
-
-    public void testDecideRequestsFirstTestMillis() throws Exception {
-        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
-        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
-        cad.addConfigAttribute(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_MILLIS"));
-
-        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
-        SecurityContextHolder.setContext(context);
-
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
-        epoint.setCaptchaFormUrl("/jcaptcha.do");
-        epoint.setIncludeOriginalRequest(false);
-
-        processor.setEntryPoint(epoint);
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setQueryString("info=true");
-        request.setServerName("localhost");
-        request.setContextPath("/demo");
-        request.setServletPath("/restricted");
-        request.setScheme("http");
-        request.setServerPort(8000);
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain();
-        FilterInvocation fi = new FilterInvocation(request, response, chain);
-
-        processor.decide(fi, cad);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeFirstTest(1);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeFirstTest(2);
-        processor.setMaxRequestsBeforeReTest(0);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-    }
-
-    public void testDecideRequestsReTest() throws Exception {
-        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
-        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
-        cad.addConfigAttribute(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_REQUESTS"));
-
-        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
-        SecurityContextHolder.setContext(context);
-
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
-        epoint.setCaptchaFormUrl("/jcaptcha.do");
-        epoint.setIncludeOriginalRequest(false);
-
-        processor.setEntryPoint(epoint);
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setQueryString("info=true");
-        request.setServerName("localhost");
-        request.setContextPath("/demo");
-        request.setServletPath("/restricted");
-        request.setScheme("http");
-        request.setServerPort(8000);
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain();
-        FilterInvocation fi = new FilterInvocation(request, response, chain);
-
-        processor.decide(fi, cad);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeFirstTest(1);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(response.getRedirectedUrl(), null);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeReTest(2);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxMillisBeforeReTest(0);
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-    }
-
-    private MockHttpServletResponse decideWithNewResponse(
-            ConfigAttributeDefinition cad, CaptchaChannelProcessor processor,
-            MockHttpServletRequest request) throws IOException,
-            ServletException {
-        MockHttpServletResponse response;
-        MockFilterChain chain;
-        FilterInvocation fi;
-        response = new MockHttpServletResponse();
-        chain = new MockFilterChain();
-        fi = new FilterInvocation(request, response, chain);
-        processor.decide(fi, cad);
-        return response;
-    }
-
-    public void testDecideRejectsNulls() throws Exception {
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        processor.setEntryPoint(new CaptchaEntryPoint());
-        processor.afterPropertiesSet();
-
-        try {
-            processor.decide(null, null);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-    }
-
-    public void testDecideMillis() throws Exception {
-        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
-        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
-        cad.addConfigAttribute(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_MILLIS"));
-
-        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
-        SecurityContextHolder.setContext(context);
-
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
-        epoint.setCaptchaFormUrl("/jcaptcha.do");
-        epoint.setIncludeOriginalRequest(false);
-
-        processor.setEntryPoint(epoint);
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setQueryString("info=true");
-        request.setServerName("localhost");
-        request.setContextPath("/demo");
-        request.setServletPath("/restricted");
-        request.setScheme("http");
-        request.setServerPort(8000);
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain();
-        FilterInvocation fi = new FilterInvocation(request, response, chain);
-
-        processor.decide(fi, cad);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeFirstTest(1);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(response.getRedirectedUrl(), null);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxMillisBeforeReTest(100);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-       waitFor(100);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeReTest(0);
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-       waitFor(100);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-       waitFor(100);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-    }
-
-    public void testDecideBoth() throws Exception {
-        ConfigAttributeDefinition cad = new ConfigAttributeDefinition();
-        cad.addConfigAttribute(new SecurityConfig("SOME_IGNORED_ATTRIBUTE"));
-        cad.addConfigAttribute(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_MILLIS"));
-        cad.addConfigAttribute(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_REQUESTS"));
-
-        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
-        SecurityContextHolder.setContext(context);
-
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        CaptchaEntryPoint epoint = new CaptchaEntryPoint();
-        epoint.setCaptchaFormUrl("/jcaptcha.do");
-        epoint.setIncludeOriginalRequest(false);
-
-        processor.setEntryPoint(epoint);
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setQueryString("info=true");
-        request.setServerName("localhost");
-        request.setContextPath("/demo");
-        request.setServletPath("/restricted");
-        request.setScheme("http");
-        request.setServerPort(8000);
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain();
-        FilterInvocation fi = new FilterInvocation(request, response, chain);
-
-        processor.decide(fi, cad);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxRequestsBeforeFirstTest(1);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(response.getRedirectedUrl(), null);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        processor.setMaxMillisBeforeReTest(100);
-        processor.setMaxRequestsBeforeReTest(2);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-       waitFor(100);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        context.setHuman();
-        SecurityContextHolder.setContext(context);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals(null, response.getRedirectedUrl());
-
-       waitFor(100);
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-
-        response = decideWithNewResponse(cad, processor, request);
-        assertEquals("http://localhost:8000/demo/jcaptcha.do", response
-                .getRedirectedUrl());
-    }
-
-    public void testGettersSetters() {
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        assertEquals("REQUIRES_HUMAN_AFTER_MAX_MILLIS", processor
-                .getRequiresHumanAfterMaxMillisKeyword());
-        processor.setRequiresHumanAfterMaxMillisKeyword("X");
-        assertEquals("X", processor.getRequiresHumanAfterMaxMillisKeyword());
-
-        assertEquals("REQUIRES_HUMAN_AFTER_MAX_REQUESTS", processor
-                .getRequiresHumanAfterMaxRequestsKeyword());
-        processor.setRequiresHumanAfterMaxRequestsKeyword("Y");
-        assertEquals("Y", processor.getRequiresHumanAfterMaxRequestsKeyword());
-
-        assertEquals(0, processor.getMaxRequestsBeforeFirstTest());
-        processor.setMaxRequestsBeforeFirstTest(1);
-        assertEquals(1, processor.getMaxRequestsBeforeFirstTest());
-
-        assertEquals(-1, processor.getMaxRequestsBeforeReTest());
-        processor.setMaxRequestsBeforeReTest(11);
-        assertEquals(11, processor.getMaxRequestsBeforeReTest());
-
-        assertEquals(-1, processor.getMaxMillisBeforeReTest());
-        processor.setMaxMillisBeforeReTest(111);
-        assertEquals(111, processor.getMaxMillisBeforeReTest());
-
-        assertTrue(processor.getEntryPoint() == null);
-        processor.setEntryPoint(new CaptchaEntryPoint());
-        assertTrue(processor.getEntryPoint() != null);
-    }
-
-    public void testMissingEntryPoint() throws Exception {
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        processor.setEntryPoint(null);
-
-        try {
-            processor.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertEquals("entryPoint required", expected.getMessage());
-        }
-    }
-
-    public void testMissingKeyword() throws Exception {
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        processor.setRequiresHumanAfterMaxMillisKeyword(null);
-
-        try {
-            processor.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-
-        }
-        processor.setRequiresHumanAfterMaxMillisKeyword("");
-
-        try {
-            processor.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-
-        }
-        processor.setRequiresHumanAfterMaxRequestsKeyword("");
-
-        try {
-            processor.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-
-        }
-
-        processor.setRequiresHumanAfterMaxRequestsKeyword(null);
-
-        try {
-            processor.afterPropertiesSet();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-
-        }
-
-    }
-
-    public void testSupports() {
-        CaptchaChannelProcessor processor = new CaptchaChannelProcessor();
-        assertTrue(processor.supports(new SecurityConfig(processor
-                .getRequiresHumanAfterMaxMillisKeyword())));
-        assertTrue(processor.supports(new SecurityConfig(processor
-                .getRequiresHumanAfterMaxRequestsKeyword())));
-
-        assertTrue(processor.supports(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_REQUESTS")));
-        assertTrue(processor.supports(new SecurityConfig(
-                "REQUIRES_HUMAN_AFTER_MAX_MILLIS")));
-
-        assertFalse(processor.supports(null));
-
-        assertFalse(processor.supports(new SecurityConfig("NOT_SUPPORTED")));
-    }
-
-    private void waitFor(int time){
-        long start=System.currentTimeMillis();
-        while(System.currentTimeMillis()<start+time){
-        }
-        return;
-    }
-
-}

+ 84 - 97
core/src/test/java/org/acegisecurity/captcha/CaptchaEntryPointTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -16,15 +16,19 @@
 package net.sf.acegisecurity.captcha;
 
 import junit.framework.TestCase;
+
 import net.sf.acegisecurity.MockPortResolver;
 import net.sf.acegisecurity.util.PortMapperImpl;
+
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 
 import java.net.URLEncoder;
+
 import java.util.HashMap;
 import java.util.Map;
 
+
 /**
  * Tests {@link CaptchaEntryPoint}.
  *
@@ -32,9 +36,10 @@ import java.util.Map;
  * @version $Id$
  */
 public class CaptchaEntryPointTests extends TestCase {
+    //~ Methods ================================================================
+
     // ~ Methods
     // ================================================================
-
     public final void setUp() throws Exception {
         super.setUp();
     }
@@ -52,8 +57,8 @@ public class CaptchaEntryPointTests extends TestCase {
             ep.afterPropertiesSet();
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
-            assertEquals("captchaFormUrl must be specified", expected
-                    .getMessage());
+            assertEquals("captchaFormUrl must be specified",
+                expected.getMessage());
         }
     }
 
@@ -79,10 +84,8 @@ public class CaptchaEntryPointTests extends TestCase {
             ep.afterPropertiesSet();
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
-            assertEquals("portResolver must be specified", expected
-                    .getMessage());
+            assertEquals("portResolver must be specified", expected.getMessage());
         }
-
     }
 
     public void testGettersSetters() {
@@ -94,7 +97,8 @@ public class CaptchaEntryPointTests extends TestCase {
         assertTrue(ep.getPortMapper() != null);
         assertTrue(ep.getPortResolver() != null);
 
-        assertEquals("original_requestUrl", ep.getOriginalRequestUrlParameterName());
+        assertEquals("original_requestUrl",
+            ep.getOriginalRequestUrlParameterName());
         ep.setOriginalRequestUrlParameterName("Z");
         assertEquals("Z", ep.getOriginalRequestUrlParameterName());
 
@@ -110,10 +114,10 @@ public class CaptchaEntryPointTests extends TestCase {
         assertFalse(ep.getForceHttps());
         ep.setForceHttps(true);
         assertTrue(ep.getForceHttps());
-
     }
 
-    public void testHttpsOperationFromOriginalHttpUrl() throws Exception {
+    public void testHttpsOperationFromOriginalHttpUrl()
+        throws Exception {
         MockHttpServletRequest request = new MockHttpServletRequest();
 
         request.setRequestURI("/some_path");
@@ -134,22 +138,22 @@ public class CaptchaEntryPointTests extends TestCase {
         ep.afterPropertiesSet();
 
         ep.commence(request, response);
-        assertEquals("https://www.example.com/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("https://www.example.com/bigWebApp/hello",
+            response.getRedirectedUrl());
 
         request.setServerPort(8080);
         response = new MockHttpServletResponse();
         ep.setPortResolver(new MockPortResolver(8080, 8443));
         ep.commence(request, response);
-        assertEquals("https://www.example.com:8443/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("https://www.example.com:8443/bigWebApp/hello",
+            response.getRedirectedUrl());
 
         // Now test an unusual custom HTTP:HTTPS is handled properly
         request.setServerPort(8888);
         response = new MockHttpServletResponse();
         ep.commence(request, response);
-        assertEquals("https://www.example.com:8443/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("https://www.example.com:8443/bigWebApp/hello",
+            response.getRedirectedUrl());
 
         PortMapperImpl portMapper = new PortMapperImpl();
         Map map = new HashMap();
@@ -168,11 +172,12 @@ public class CaptchaEntryPointTests extends TestCase {
         ep.afterPropertiesSet();
 
         ep.commence(request, response);
-        assertEquals("https://www.example.com:9999/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("https://www.example.com:9999/bigWebApp/hello",
+            response.getRedirectedUrl());
     }
 
-    public void testHttpsOperationFromOriginalHttpsUrl() throws Exception {
+    public void testHttpsOperationFromOriginalHttpsUrl()
+        throws Exception {
         MockHttpServletRequest request = new MockHttpServletRequest();
 
         request.setRequestURI("/some_path");
@@ -193,15 +198,15 @@ public class CaptchaEntryPointTests extends TestCase {
         ep.afterPropertiesSet();
 
         ep.commence(request, response);
-        assertEquals("https://www.example.com/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("https://www.example.com/bigWebApp/hello",
+            response.getRedirectedUrl());
 
         request.setServerPort(8443);
         response = new MockHttpServletResponse();
         ep.setPortResolver(new MockPortResolver(8080, 8443));
         ep.commence(request, response);
-        assertEquals("https://www.example.com:8443/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("https://www.example.com:8443/bigWebApp/hello",
+            response.getRedirectedUrl());
     }
 
     public void testNormalOperation() throws Exception {
@@ -212,7 +217,6 @@ public class CaptchaEntryPointTests extends TestCase {
         ep.afterPropertiesSet();
         ep.setIncludeOriginalRequest(false);
 
-
         MockHttpServletRequest request = new MockHttpServletRequest();
         request.setRequestURI("/some_path");
         request.setContextPath("/bigWebApp");
@@ -225,12 +229,12 @@ public class CaptchaEntryPointTests extends TestCase {
 
         ep.afterPropertiesSet();
         ep.commence(request, response);
-        assertEquals("http://www.example.com/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("http://www.example.com/bigWebApp/hello",
+            response.getRedirectedUrl());
     }
 
     public void testOperationWhenHttpsRequestsButHttpsPortUnknown()
-            throws Exception {
+        throws Exception {
         CaptchaEntryPoint ep = new CaptchaEntryPoint();
         ep.setCaptchaFormUrl("/hello");
         ep.setPortMapper(new PortMapperImpl());
@@ -255,16 +259,18 @@ public class CaptchaEntryPointTests extends TestCase {
 
         // Response doesn't switch to HTTPS, as we didn't know HTTP port 8888 to
         // HTTP port mapping
-        assertEquals("http://www.example.com:8888/bigWebApp/hello", response
-                .getRedirectedUrl());
+        assertEquals("http://www.example.com:8888/bigWebApp/hello",
+            response.getRedirectedUrl());
     }
 
-    public void testOperationWithOriginalRequestIncludes() throws Exception {
+    public void testOperationWithOriginalRequestIncludes()
+        throws Exception {
         CaptchaEntryPoint ep = new CaptchaEntryPoint();
         ep.setCaptchaFormUrl("/hello");
+
         PortMapperImpl mapper = new PortMapperImpl();
         mapper.getTranslatedPortMappings().put(new Integer(8888),
-                new Integer(1234));
+            new Integer(1234));
         ep.setPortMapper(mapper);
 
         ep.setPortResolver(new MockPortResolver(8888, 1234));
@@ -276,30 +282,26 @@ public class CaptchaEntryPointTests extends TestCase {
         request.setRequestURI("/some_path");
         request.setScheme("http");
         request.setServerName("www.example.com");
+
         // request.setContextPath("/bigWebApp");
         // TODO correct this when the getRequestUrl from mock works...
-
         request.setServerPort(8888); // NB: Port we can't resolve
 
         MockHttpServletResponse response = new MockHttpServletResponse();
 
         ep.afterPropertiesSet();
         ep.commence(request, response);
-        assertEquals(
-                "http://www.example.com:8888/hello?original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
-                        + "&original_request_method=post",
-                response.getRedirectedUrl());
+        assertEquals("http://www.example.com:8888/hello?original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post", response.getRedirectedUrl());
 
         // test the query params
         request.addParameter("name", "value");
         response = new MockHttpServletResponse();
         ep.commence(request, response);
-        assertEquals(
-                "http://www.example.com:8888/hello?original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
-                        + "&original_request_method=post",
-                response.getRedirectedUrl());
+        assertEquals("http://www.example.com:8888/hello?original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post", response.getRedirectedUrl());
 
         // test the multiple query params
         ep.setIncludeOriginalParameters(true);
@@ -308,46 +310,42 @@ public class CaptchaEntryPointTests extends TestCase {
         request.addParameter("name1", "value2");
         response = new MockHttpServletResponse();
         ep.commence(request, response);
-        assertEquals(
-                "http://www.example.com:8888/hello?original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
-                        + "&original_request_method=post"
-                        + "&original_request_parameters="
-                        + URLEncoder.encode("name@@value;;name1@@value2", "UTF-8"),
-                response.getRedirectedUrl());
+        assertEquals("http://www.example.com:8888/hello?original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post" + "&original_request_parameters="
+            + URLEncoder.encode("name__value;;name1__value2", "UTF-8"),
+            response.getRedirectedUrl());
 
         // test add parameter to captcha form url??
-
         ep.setCaptchaFormUrl("/hello?toto=titi");
         response = new MockHttpServletResponse();
         ep.commence(request, response);
         assertEquals(
-                "http://www.example.com:8888/hello?toto=titi&original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
-                        + "&original_request_method=post" + "&original_request_parameters="
-                        + URLEncoder.encode("name@@value;;name1@@value2", "UTF-8"),
-                response.getRedirectedUrl());
+            "http://www.example.com:8888/hello?toto=titi&original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post" + "&original_request_parameters="
+            + URLEncoder.encode("name__value;;name1__value2", "UTF-8"),
+            response.getRedirectedUrl());
 
         // with forcing!!!
         ep.setForceHttps(true);
         response = new MockHttpServletResponse();
         ep.commence(request, response);
         assertEquals(
-                "https://www.example.com:1234/hello?toto=titi&original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8") +
-                        "&original_request_method=post"
-                        + "&original_request_parameters="
-                        + URLEncoder.encode("name@@value;;name1@@value2", "UTF-8"),
-                response.getRedirectedUrl());
-
+            "https://www.example.com:1234/hello?toto=titi&original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post" + "&original_request_parameters="
+            + URLEncoder.encode("name__value;;name1__value2", "UTF-8"),
+            response.getRedirectedUrl());
     }
 
     public void testOperationWithOutsideWebApp() throws Exception {
         CaptchaEntryPoint ep = new CaptchaEntryPoint();
         ep.setCaptchaFormUrl("https://www.jcaptcha.net/dotest/");
+
         PortMapperImpl mapper = new PortMapperImpl();
         mapper.getTranslatedPortMappings().put(new Integer(8888),
-                new Integer(1234));
+            new Integer(1234));
         ep.setPortMapper(mapper);
 
         ep.setPortResolver(new MockPortResolver(8888, 1234));
@@ -355,36 +353,32 @@ public class CaptchaEntryPointTests extends TestCase {
         ep.setOutsideWebApp(true);
 
         ep.afterPropertiesSet();
+
         MockHttpServletRequest request = new MockHttpServletRequest();
         request.setRequestURI("/some_path");
         request.setScheme("http");
         request.setServerName("www.example.com");
         request.setMethod("post");
+
         // request.setContextPath("/bigWebApp");
         // TODO correct this when the getRequestUrl from mock works...
-
         request.setServerPort(8888); // NB: Port we can't resolve
 
         MockHttpServletResponse response = new MockHttpServletResponse();
 
         ep.afterPropertiesSet();
         ep.commence(request, response);
-        assertEquals(
-                "https://www.jcaptcha.net/dotest/?original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
-                        + "&original_request_method=post"
-                ,
-                response.getRedirectedUrl());
+        assertEquals("https://www.jcaptcha.net/dotest/?original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post", response.getRedirectedUrl());
 
         // test the query params
         request.addParameter("name", "value");
         response = new MockHttpServletResponse();
         ep.commence(request, response);
-        assertEquals(
-                "https://www.jcaptcha.net/dotest/?original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8") +
-                        "&original_request_method=post",
-                response.getRedirectedUrl());
+        assertEquals("https://www.jcaptcha.net/dotest/?original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post", response.getRedirectedUrl());
 
         // test the multiple query params
         ep.setIncludeOriginalParameters(true);
@@ -392,39 +386,32 @@ public class CaptchaEntryPointTests extends TestCase {
         request.addParameter("name1", "value2");
         response = new MockHttpServletResponse();
         ep.commence(request, response);
-        assertEquals(
-                "https://www.jcaptcha.net/dotest/?original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
-                        + "&original_request_method=post"
-                        + "&original_request_parameters="
-                        + URLEncoder.encode("name@@value;;name1@@value2", "UTF-8"),
-                response.getRedirectedUrl());
+        assertEquals("https://www.jcaptcha.net/dotest/?original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post" + "&original_request_parameters="
+            + URLEncoder.encode("name__value;;name1__value2", "UTF-8"),
+            response.getRedirectedUrl());
 
         // test add parameter to captcha form url??
-
         ep.setCaptchaFormUrl("https://www.jcaptcha.net/dotest/?toto=titi");
         response = new MockHttpServletResponse();
         ep.commence(request, response);
         assertEquals(
-                "https://www.jcaptcha.net/dotest/?toto=titi&original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8") +
-                        "&original_request_method=post"
-                        + "&original_request_parameters="
-                        + URLEncoder.encode("name@@value;;name1@@value2", "UTF-8"),
-                response.getRedirectedUrl());
+            "https://www.jcaptcha.net/dotest/?toto=titi&original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post" + "&original_request_parameters="
+            + URLEncoder.encode("name__value;;name1__value2", "UTF-8"),
+            response.getRedirectedUrl());
 
         // with forcing!!!
         ep.setForceHttps(true);
         response = new MockHttpServletResponse();
         ep.commence(request, response);
         assertEquals(
-                "https://www.jcaptcha.net/dotest/?toto=titi&original_requestUrl="
-                        + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8") +
-                        "&original_request_method=post"
-                        + "&original_request_parameters="
-                        + URLEncoder.encode("name@@value;;name1@@value2", "UTF-8"),
-                response.getRedirectedUrl());
-
+            "https://www.jcaptcha.net/dotest/?toto=titi&original_requestUrl="
+            + URLEncoder.encode("http://www.example.com:8888/some_path", "UTF-8")
+            + "&original_request_method=post" + "&original_request_parameters="
+            + URLEncoder.encode("name__value;;name1__value2", "UTF-8"),
+            response.getRedirectedUrl());
     }
-
 }

+ 34 - 37
core/src/test/java/org/acegisecurity/captcha/CaptchaSecurityContextImplTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -17,6 +17,7 @@ package net.sf.acegisecurity.captcha;
 
 import net.sf.acegisecurity.context.SecurityContextImplTests;
 
+
 /**
  * Tests {@link CaptchaSecurityContextImpl}.
  *
@@ -24,62 +25,58 @@ import net.sf.acegisecurity.context.SecurityContextImplTests;
  * @version $Id$
  */
 public class CaptchaSecurityContextImplTests extends SecurityContextImplTests {
+    //~ Methods ================================================================
 
     public void testDefaultValues() {
         CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
         assertEquals("should not be human", false, context.isHuman());
-        assertEquals("should be 0", 0, context
-                .getLastPassedCaptchaDateInMillis());
-        assertEquals("should be 0", 0, context
-                .getHumanRestrictedResourcesRequestsCount());
-    }
-
-    public void testSetHuman() {
-        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
-        long now = System.currentTimeMillis();
-        context.setHuman();
-        assertEquals("should be human", true, context.isHuman());
-        assertTrue("should be more than 0", context
-                .getLastPassedCaptchaDateInMillis()
-                - now >= 0);
-        assertTrue("should be less than 0,1 seconde", context
-                .getLastPassedCaptchaDateInMillis()
-                - now < 100);
-        assertEquals("should be 0", 0, context
-                .getHumanRestrictedResourcesRequestsCount());
+        assertEquals("should be 0", 0,
+            context.getLastPassedCaptchaDateInMillis());
+        assertEquals("should be 0", 0,
+            context.getHumanRestrictedResourcesRequestsCount());
     }
 
     public void testIncrementRequests() {
         CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
         context.setHuman();
         assertEquals("should be human", true, context.isHuman());
-        assertEquals("should be 0", 0, context
-                .getHumanRestrictedResourcesRequestsCount());
+        assertEquals("should be 0", 0,
+            context.getHumanRestrictedResourcesRequestsCount());
         context.incrementHumanRestrictedRessoucesRequestsCount();
-        assertEquals("should be 1", 1, context
-                .getHumanRestrictedResourcesRequestsCount());
+        assertEquals("should be 1", 1,
+            context.getHumanRestrictedResourcesRequestsCount());
     }
 
     public void testResetHuman() {
         CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
         context.setHuman();
         assertEquals("should be human", true, context.isHuman());
-        assertEquals("should be 0", 0, context
-                .getHumanRestrictedResourcesRequestsCount());
+        assertEquals("should be 0", 0,
+            context.getHumanRestrictedResourcesRequestsCount());
         context.incrementHumanRestrictedRessoucesRequestsCount();
-        assertEquals("should be 1", 1, context
-                .getHumanRestrictedResourcesRequestsCount());
+        assertEquals("should be 1", 1,
+            context.getHumanRestrictedResourcesRequestsCount());
+
         long now = System.currentTimeMillis();
         context.setHuman();
-        assertEquals("should be 0", 0, context
-                .getHumanRestrictedResourcesRequestsCount());
-        assertTrue("should be more than 0", context
-                .getLastPassedCaptchaDateInMillis()
-                - now >= 0);
-        assertTrue("should be less than 0,1 seconde", context
-                .getLastPassedCaptchaDateInMillis()
-                - now < 100);
-
+        assertEquals("should be 0", 0,
+            context.getHumanRestrictedResourcesRequestsCount());
+        assertTrue("should be more than 0",
+            (context.getLastPassedCaptchaDateInMillis() - now) >= 0);
+        assertTrue("should be less than 0,1 seconde",
+            (context.getLastPassedCaptchaDateInMillis() - now) < 100);
     }
 
+    public void testSetHuman() {
+        CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+        long now = System.currentTimeMillis();
+        context.setHuman();
+        assertEquals("should be human", true, context.isHuman());
+        assertTrue("should be more than 0",
+            (context.getLastPassedCaptchaDateInMillis() - now) >= 0);
+        assertTrue("should be less than 0,1 seconde",
+            (context.getLastPassedCaptchaDateInMillis() - now) < 100);
+        assertEquals("should be 0", 0,
+            context.getHumanRestrictedResourcesRequestsCount());
+    }
 }

+ 31 - 33
core/src/test/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilterTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -16,10 +16,13 @@
 package net.sf.acegisecurity.captcha;
 
 import junit.framework.TestCase;
+
 import net.sf.acegisecurity.context.SecurityContextHolder;
 import net.sf.acegisecurity.util.MockFilterChain;
+
 import org.springframework.mock.web.MockHttpServletRequest;
 
+
 /**
  * Tests {@link CaptchaValidationProcessingFilter}.
  *
@@ -27,9 +30,10 @@ import org.springframework.mock.web.MockHttpServletRequest;
  * @version $Id$
  */
 public class CaptchaValidationProcessingFilterTests extends TestCase {
+    //~ Methods ================================================================
 
     /*
-      */
+     */
     public void testAfterPropertiesSet() throws Exception {
         CaptchaValidationProcessingFilter filter = new CaptchaValidationProcessingFilter();
 
@@ -38,78 +42,72 @@ public class CaptchaValidationProcessingFilterTests extends TestCase {
             fail("should have thrown an invalid argument exception");
         } catch (Exception e) {
             assertTrue("should be an InvalidArgumentException",
-                    IllegalArgumentException.class.isAssignableFrom(e
-                            .getClass()));
+                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
         }
 
         filter.setCaptchaService(new MockCaptchaServiceProxy());
         filter.afterPropertiesSet();
         filter.setCaptchaValidationParameter(null);
+
         try {
             filter.afterPropertiesSet();
             fail("should have thrown an invalid argument exception");
         } catch (Exception e) {
             assertTrue("should be an InvalidArgumentException",
-                    IllegalArgumentException.class.isAssignableFrom(e
-                            .getClass()));
+                IllegalArgumentException.class.isAssignableFrom(e.getClass()));
         }
-
-
     }
 
     /*
-      * Test method for
-      * 'net.sf.acegisecurity.captcha.CaptchaValidationProcessingFilter.doFilter(ServletRequest,
-      * ServletResponse, FilterChain)'
-      */
-    public void testDoFilterWithoutRequestParameter() throws Exception {
+     * Test method for
+     * 'net.sf.acegisecurity.captcha.CaptchaValidationProcessingFilter.doFilter(ServletRequest,
+     * ServletResponse, FilterChain)'
+     */
+    public void testDoFilterWithRequestParameter() throws Exception {
         CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
         SecurityContextHolder.setContext(context);
+
         MockHttpServletRequest request = new MockHttpServletRequest();
+
         CaptchaValidationProcessingFilter filter = new CaptchaValidationProcessingFilter();
+        request.addParameter(filter.getCaptchaValidationParameter(), "");
+
         MockCaptchaServiceProxy service = new MockCaptchaServiceProxy();
         MockFilterChain chain = new MockFilterChain(true);
         filter.setCaptchaService(service);
         filter.doFilter(request, null, chain);
-        assertFalse("proxy should not have been called", service.hasBeenCalled);
+        assertTrue("should have been called", service.hasBeenCalled);
         assertFalse("context should not have been updated", context.isHuman());
+
         // test with valid
         service.valid = true;
         filter.doFilter(request, null, chain);
-        assertFalse("proxy should not have been called", service.hasBeenCalled);
-        assertFalse("context should not have been updated", context.isHuman());
-
+        assertTrue("should have been called", service.hasBeenCalled);
+        assertTrue("context should have been updated", context.isHuman());
     }
 
     /*
-      * Test method for
-      * 'net.sf.acegisecurity.captcha.CaptchaValidationProcessingFilter.doFilter(ServletRequest,
-      * ServletResponse, FilterChain)'
-      */
-    public void testDoFilterWithRequestParameter() throws Exception {
+     * Test method for
+     * 'net.sf.acegisecurity.captcha.CaptchaValidationProcessingFilter.doFilter(ServletRequest,
+     * ServletResponse, FilterChain)'
+     */
+    public void testDoFilterWithoutRequestParameter() throws Exception {
         CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
         SecurityContextHolder.setContext(context);
 
         MockHttpServletRequest request = new MockHttpServletRequest();
-
         CaptchaValidationProcessingFilter filter = new CaptchaValidationProcessingFilter();
-        request
-                .addParameter(
-                        filter.getCaptchaValidationParameter(),
-                        "");
-
         MockCaptchaServiceProxy service = new MockCaptchaServiceProxy();
         MockFilterChain chain = new MockFilterChain(true);
         filter.setCaptchaService(service);
         filter.doFilter(request, null, chain);
-        assertTrue("should have been called", service.hasBeenCalled);
+        assertFalse("proxy should not have been called", service.hasBeenCalled);
         assertFalse("context should not have been updated", context.isHuman());
+
         // test with valid
         service.valid = true;
         filter.doFilter(request, null, chain);
-        assertTrue("should have been called", service.hasBeenCalled);
-        assertTrue("context should have been updated", context.isHuman());
-
+        assertFalse("proxy should not have been called", service.hasBeenCalled);
+        assertFalse("context should not have been updated", context.isHuman());
     }
-
 }

+ 7 - 4
core/src/test/java/org/acegisecurity/captcha/MockCaptchaServiceProxy.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 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.
@@ -16,19 +16,22 @@
 package net.sf.acegisecurity.captcha;
 
 /**
+ * DOCUMENT ME!
+ *
  * @author marc antoine Garrigue
  * @version $Id$
  */
 public class MockCaptchaServiceProxy implements CaptchaServiceProxy {
+    //~ Instance fields ========================================================
 
+    public boolean hasBeenCalled = false;
     public boolean valid = false;
 
-    public boolean hasBeenCalled = false;
+    //~ Methods ================================================================
 
     public boolean validateReponseForId(String id, Object response) {
         hasBeenCalled = true;
-        return valid;
 
+        return valid;
     }
-
 }

+ 84 - 0
core/src/test/java/org/acegisecurity/captcha/TestOnceAfterMaxRequestsCaptchaChannelProcessorTests.java

@@ -0,0 +1,84 @@
+/* Copyright 2004, 2005 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.captcha;
+
+import junit.framework.*;
+
+import net.sf.acegisecurity.captcha.TestOnceAfterMaxRequestsCaptchaChannelProcessor;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author $author$
+ * @version $Revision$
+ */
+public class TestOnceAfterMaxRequestsCaptchaChannelProcessorTests
+    extends TestCase {
+    //~ Instance fields ========================================================
+
+    TestOnceAfterMaxRequestsCaptchaChannelProcessor testOnceAfterMaxRequestsCaptchaChannelProcessor;
+
+    //~ Methods ================================================================
+
+    public void testIsContextValidConcerningHumanity()
+        throws Exception {
+        testOnceAfterMaxRequestsCaptchaChannelProcessor.setThresold(1);
+
+        CaptchaSecurityContextImpl context = new CaptchaSecurityContextImpl();
+        assertTrue(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+
+        testOnceAfterMaxRequestsCaptchaChannelProcessor.setThresold(-1);
+        assertFalse(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+
+        testOnceAfterMaxRequestsCaptchaChannelProcessor.setThresold(3);
+        assertTrue(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertTrue(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        context.incrementHumanRestrictedRessoucesRequestsCount();
+        assertFalse(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        context.setHuman();
+
+        for (int i = 0;
+            i < (2 * testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .getThresold()); i++) {
+            assertTrue(testOnceAfterMaxRequestsCaptchaChannelProcessor
+                .isContextValidConcerningHumanity(context));
+        }
+    }
+
+    public void testNewContext() {
+        CaptchaSecurityContextImpl context = new CaptchaSecurityContextImpl();
+
+        assertFalse(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+        testOnceAfterMaxRequestsCaptchaChannelProcessor.setThresold(1);
+        assertTrue(testOnceAfterMaxRequestsCaptchaChannelProcessor
+            .isContextValidConcerningHumanity(context));
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        testOnceAfterMaxRequestsCaptchaChannelProcessor = new TestOnceAfterMaxRequestsCaptchaChannelProcessor();
+    }
+}