diff --git a/core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessor.java b/core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessor.java
new file mode 100644
index 0000000000..feb0f3d806
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/captcha/CaptchaChannelProcessor.java
@@ -0,0 +1,361 @@
+/* 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;
+
+/**
+ *
+ * Ensures the user has enougth human privileges by review of the
+ * {@link net.sf.acegisecurity.captcha.CaptchaSecurityContext}.
+ *
+ *
+ *
+ * The class takes 3 required attributes :
+ *
+ * - maxRequestsBeforeFirstTest : used by
+ * {@link #getRequiresHumanAfterMaxRequestsKeyword()} and
+ * {@link #getRequiresHumanAfterMaxMillisKeyword()}
+ * default value = 0 (ie first request).
+ * - maxRequestsBeforeReTest : used by
+ * {@link #getRequiresHumanAfterMaxMillisKeyword()}
+ * default value = -1 (ie disabled, once in a {@link CaptchaSecurityContext}'s
+ * life).
+ * - maxMillisBeforeReTest: used by
+ * {@link #getRequiresHumanAfterMaxMillisKeyword()}
+ * default value = -1 (ie disabled, once in a {@link CaptchaSecurityContext}'s
+ * life).
+ *
+ * The class responds to two case-sensitive keywords :
+ *
+ * - {@link #getRequiresHumanAfterMaxRequestsKeyword()}
+ * default value = REQUIRES_HUMAN_AFTER_MAX_REQUESTS
+ * if detected, checks if :
+ *
+ *
+ * {@link CaptchaSecurityContext#isHuman()} == true
+ * - or
+ * {@link CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()} < maxRequestsBeforeFirstTest
+ *
+ * - and
+ *
+ * {@link CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()} < maxRequestsBeforeReTest
+ * - or
+ * maxRequestsBeforeReTest < 0
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * - {@link #getRequiresHumanUntilMaxRequestsKeyword()}
+ * default value = REQUIRES_HUMAN_AFTER_MAX_MILLIS
+ * if detected, checks if :
+ *
+ *
+ *
+ * {@link CaptchaSecurityContext#isHuman()} == true
+ * - or
+ * {@link CaptchaSecurityContext#getHumanRestrictedResourcesRequestsCount()} < =maxRequestsBeforeFirstTest
+ *
+ * - and
+ *
+ * System.currentTimeMillis()-{@link CaptchaSecurityContext#getLastPassedCaptchaDateInMillis()} <= maxMillisBeforeReTest
+ * - or
+ * maxMillisBeforeReTest < 0
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * 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})
+ *
+ *
+ * - Once in a session and at first request : use the
+ * REQUIRES_HUMAN_AFTER_MAX_REQUESTS keyword
+ * with a maxRequestsBeforeFirstTest=0
+ * and a maxRequestsBeforeReTest=-1
+ *
+ *
+ *
+ * - Once in a session and only after 3 requests : use the
+ * REQUIRES_HUMAN_AFTER_MAX_REQUESTS keyword
+ * with a maxRequestsBeforeFirstTest=3
+ * and a maxRequestsBeforeReTest=-1
+ *
+ *
+ * - Every request and only after 5 requests : use the
+ * REQUIRES_HUMAN_AFTER_MAX_REQUESTS
+ * with a maxRequestsBeforeReTest=0
+ * and a maxRequestsBeforeFirstTest=5
+ *
+ *
+ * - Every 3 requests and every minute : use the
+ * REQUIRES_HUMAN_AFTER_MAX_MILLIS keywords
+ * with a maxMillisBeforeReTest=6000
+ * and a maxRequestsBeforeFirstTest=3
+ *
+ *
+ * - Every 20 requests and every hour and only after 100 requests : use the
+ *
+ * REQUIRES_HUMAN_AFTER_MAX_REQUESTS
+ * and the REQUIRES_HUMAN_AFTER_MAX_MILLIS
+ * and the REQUIRES_HUMAN_AFTER_MAX_REQUESTS keywords
+ * with a maxRequestsBeforeReTest=20
+ * and a maxMillisBeforeReTest=3600000
+ * and amaxRequestsBeforeFirstTest=1000
+ *
+ *
+ *
+ *
+ * @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 ChannelEntryPoint entryPoint;
+
+ private int maxRequestsBeforeReTest = -1;
+
+ private int maxRequestsBeforeFirstTest = 0;
+
+ private long maxMillisBeforeReTest = -1;
+
+ 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 = (CaptchaSecurityContext) SecurityContextHolder
+ .getContext();
+
+ Iterator iter = config.getConfigAttributes();
+ boolean shouldRedirect = true;
+
+ while (iter.hasNext()) {
+ ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+ if (supports(attribute)) {
+ logger.debug("supports this attribute : " + attribute);
+ if (isContextValidForAttribute(context, attribute)) {
+ shouldRedirect = false;
+ } else {
+ // reset if already passed a constraint
+
+ shouldRedirect = true;
+ // break at first unsatisfy contraint
+ break;
+ }
+
+ }
+ }
+ 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 (attribute.getAttribute().equals(
+ getRequiresHumanAfterMaxRequestsKeyword())) {
+ if (isContextValidConcerningHumanOrFirstTest(context)
+ && isContextValidConcerningReTest(context)) {
+ valid = true;
+ }
+ }
+
+ // test the REQUIRES_HUMAN_AFTER_MAX_MILLIS keyword
+ if (attribute.getAttribute().equals(
+ getRequiresHumanAfterMaxMillisKeyword())) {
+ 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)
+ && (attribute.getAttribute().equals(
+ getRequiresHumanAfterMaxRequestsKeyword()) || attribute
+ .getAttribute().equals(
+ getRequiresHumanAfterMaxMillisKeyword())
+
+ )) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/acegisecurity/captcha/CaptchaEntryPoint.java b/core/src/main/java/org/acegisecurity/captcha/CaptchaEntryPoint.java
new file mode 100644
index 0000000000..2e39d76b29
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/captcha/CaptchaEntryPoint.java
@@ -0,0 +1,287 @@
+/* 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.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.acegisecurity.securechannel.ChannelEntryPoint;
+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;
+
+/**
+ * The captcha entry point : redirect to the captcha test page.
+ *
+ * This entry point can force the use of SSL : see {@link #getForceHttps()}
+ *
+ * This entry point allows internal OR external redirect : see
+ * {@link #setOutsideWebApp(boolean)}
/ Original request can be added to
+ * the redirect path using a special parameter : see
+ * {@link #getOriginalRequestParameterName()} and
+ * {@link #setIncludeOriginalRequest()}
Default values :
+ * forceHttps = false
includesOriginalRequest = false
+ * originalRequestParameterName= "originalRequest"
isOutsideWebApp=false
+ *
+ * @author marc antoine Garrigue
+ * @version $Id$
+ */
+public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
+ // ~ Static fields/initializers
+ // =============================================
+
+ private static final Log logger = LogFactory
+ .getLog(CaptchaEntryPoint.class);
+
+ // ~ Instance fields
+ // ========================================================
+
+ private PortMapper portMapper = new PortMapperImpl();
+
+ private PortResolver portResolver = new PortResolverImpl();
+
+ private String captchaFormUrl;
+
+ private boolean forceHttps = false;
+
+ private String originalRequestParameterName = "originalRequest";
+
+ private boolean isOutsideWebApp = false;
+
+ private boolean includeOriginalRequest = 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
+ * https
, then
+ *
+ * @param forceHttps
+ */
+ public void setForceHttps(boolean forceHttps) {
+ this.forceHttps = forceHttps;
+ }
+
+ public boolean getForceHttps() {
+ return forceHttps;
+ }
+
+ /**
+ * The URL where the CaptchaProcessingFilter
login page can
+ * be found. Should be relative to the web-app context path, and include a
+ * leading /
+ *
+ * @param captchaFormUrl
+ */
+ public void setCaptchaFormUrl(String loginFormUrl) {
+ this.captchaFormUrl = loginFormUrl;
+ }
+
+ /**
+ * @return the captcha test page to redirect to.
+ */
+ public String getCaptchaFormUrl() {
+ return captchaFormUrl;
+ }
+
+ public void setPortMapper(PortMapper portMapper) {
+ this.portMapper = portMapper;
+ }
+
+ public PortMapper getPortMapper() {
+ return portMapper;
+ }
+
+ public void setPortResolver(PortResolver portResolver) {
+ this.portResolver = portResolver;
+ }
+
+ public PortResolver getPortResolver() {
+ return portResolver;
+ }
+
+ public boolean isOutsideWebApp() {
+ return isOutsideWebApp;
+ }
+
+ /**
+ * 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 String getOriginalRequestParameterName() {
+ return originalRequestParameterName;
+ }
+
+ /**
+ * sets the parameter under which the original request url will be appended
+ * to the redirect url (only if {@link #isIncludeOriginalRequest()}==true).
+ *
+ * @param originalRequestParameterName
+ */
+ public void setOriginalRequestParameterName(
+ String originalRequestParameterName) {
+ this.originalRequestParameterName = originalRequestParameterName;
+ }
+
+ public boolean isIncludeOriginalRequest() {
+ return includeOriginalRequest;
+ }
+
+ /**
+ * If set to true, the original request url will be appended to the redirect
+ * url using the {@link #getOriginalRequestParameterName()}.
+ *
+ * @param includeOriginalRequest
+ */
+ public void setIncludeOriginalRequest(boolean includeOriginalRequest) {
+ this.includeOriginalRequest = includeOriginalRequest;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.hasLength(captchaFormUrl, "captchaFormUrl must be specified");
+ Assert.notNull(portMapper, "portMapper must be specified");
+ Assert.notNull(portResolver, "portResolver must be specified");
+ }
+
+ public void commence(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException {
+ StringBuffer redirectUrl = new StringBuffer();
+ HttpServletRequest req = (HttpServletRequest) request;
+
+ if (isOutsideWebApp) {
+ redirectUrl = redirectUrl.append(captchaFormUrl);
+ } else {
+ buildInternalRedirect(redirectUrl, req);
+ }
+
+ if (includeOriginalRequest) {
+ includeOriginalRequest(redirectUrl, req);
+ }
+ // add post parameter? TODO?
+ if (logger.isDebugEnabled()) {
+ logger.debug("Redirecting to: " + redirectUrl);
+ }
+
+ ((HttpServletResponse) response)
+ .sendRedirect(((HttpServletResponse) response)
+ .encodeRedirectURL(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(originalRequestParameterName);
+ redirectUrl.append("=");
+ redirectUrl.append(req.getRequestURL().toString());
+ // append query params
+ Enumeration parameters = req.getParameterNames();
+ if (parameters != null && parameters.hasMoreElements()) {
+ redirectUrl.append("?");
+ while (parameters.hasMoreElements()) {
+ String name = parameters.nextElement().toString();
+ String value = req.getParameter(name);
+ redirectUrl.append(name);
+ redirectUrl.append("=");
+ redirectUrl.append(value);
+ if (parameters.hasMoreElements()) {
+ redirectUrl.append("&");
+ }
+ }
+ }
+
+ }
+
+ private void buildInternalRedirect(StringBuffer redirectUrl,
+ HttpServletRequest req) {
+ // construct it
+ StringBuffer simpleRedirect = new StringBuffer();
+
+ String scheme = req.getScheme();
+ String serverName = req.getServerName();
+ 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;
+ }
+
+ simpleRedirect.append(scheme);
+ simpleRedirect.append("://");
+ simpleRedirect.append(serverName);
+ if (includePort) {
+ simpleRedirect.append(":");
+ simpleRedirect.append(serverPort);
+ }
+ simpleRedirect.append(contextPath);
+ simpleRedirect.append(captchaFormUrl);
+
+ if (forceHttps && req.getScheme().equals("http")) {
+ Integer httpPort = new Integer(portResolver.getServerPort(req));
+ Integer httpsPort = (Integer) portMapper.lookupHttpsPort(httpPort);
+
+ if (httpsPort != null) {
+ if (httpsPort.intValue() == 443) {
+ includePort = false;
+ } else {
+ includePort = true;
+ }
+
+ redirectUrl.append("https://");
+ redirectUrl.append(serverName);
+ if (includePort) {
+ redirectUrl.append(":");
+ redirectUrl.append(httpsPort);
+ }
+ redirectUrl.append(contextPath);
+ redirectUrl.append(captchaFormUrl);
+ } else {
+ redirectUrl.append(simpleRedirect);
+ }
+ } else {
+ redirectUrl.append(simpleRedirect);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContext.java b/core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContext.java
new file mode 100644
index 0000000000..df0a2ac008
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContext.java
@@ -0,0 +1,55 @@
+/* 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.context.SecurityContext;
+
+/**
+ * Interface that add humanity concerns to the SecurityContext
+ *
+ * @author marc antoine garrigue
+ */
+public interface CaptchaSecurityContext extends SecurityContext {
+
+ /**
+ * @return true if the current user has already passed a captcha.
+ */
+ boolean isHuman();
+
+ /**
+ * set human attribute, should called after captcha validation.
+ *
+ * @param human
+ */
+ 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();
+}
diff --git a/core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContextImpl.java b/core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContextImpl.java
new file mode 100644
index 0000000000..112fbbd965
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/captcha/CaptchaSecurityContextImpl.java
@@ -0,0 +1,85 @@
+/* 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 net.sf.acegisecurity.context.SecurityContextImpl;
+
+/**
+ * @author mag
+ *
+ */
+public class CaptchaSecurityContextImpl extends SecurityContextImpl implements
+ CaptchaSecurityContext {
+
+ private boolean human;
+
+ private long lastPassedCaptchaDate;
+
+ private int humanRestrictedResourcesRequestsCount;
+
+ /**
+ *
+ */
+ public CaptchaSecurityContextImpl() {
+ super();
+ human = false;
+ lastPassedCaptchaDate = 0;
+ humanRestrictedResourcesRequestsCount = 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.sf.acegisecurity.context.CaptchaSecurityContext#isHuman()
+ */
+ public boolean isHuman() {
+ return human;
+ }
+
+ /**
+ * 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#getLastPassedCaptchaDateInMillis()
+ */
+ public long getLastPassedCaptchaDateInMillis() {
+
+ return lastPassedCaptchaDate;
+ }
+
+ /**
+ * Method to increment the human Restricted Resrouces Requests Count;
+ */
+ public void incrementHumanRestrictedRessoucesRequestsCount() {
+ humanRestrictedResourcesRequestsCount++;
+ };
+}
diff --git a/core/src/main/java/org/acegisecurity/captcha/CaptchaServiceProxy.java b/core/src/main/java/org/acegisecurity/captcha/CaptchaServiceProxy.java
new file mode 100644
index 0000000000..01d45b36ca
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/captcha/CaptchaServiceProxy.java
@@ -0,0 +1,17 @@
+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 {
+
+ /**
+ * @return true if the request is validated by the back end captcha service.
+ */
+ boolean validateRequest(ServletRequest request);
+}
diff --git a/core/src/main/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilter.java b/core/src/main/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilter.java
new file mode 100644
index 0000000000..d9d59e3b7f
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilter.java
@@ -0,0 +1,126 @@
+/* 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 javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+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;
+
+/**
+ * Filter for web integration of the {@link CaptchaServiceProxy}.
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.
+ *
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.
This filter should only be used in
+ * conjunction with the {@link CaptchaSecurityContext}
+ *
+ *
+ * @author marc antoine Garrigue
+ * @version $Id$
+ */
+public class CaptchaValidationProcessingFilter implements InitializingBean,
+ Filter {
+ // ~ Static fields/initializers
+ // =============================================
+ public static String CAPTCHA_VALIDATION_SECURITY_PARAMETER_KEY = "_captcha_parameter";
+
+ protected static final Log logger = LogFactory
+ .getLog(HttpSessionContextIntegrationFilter.class);
+
+ // ~ Instance fields
+ // ========================================================
+
+ private CaptchaServiceProxy captchaService;
+
+ // ~ Methods
+ // ================================================================
+
+ public CaptchaServiceProxy getCaptchaService() {
+ return captchaService;
+ }
+
+ public void setCaptchaService(CaptchaServiceProxy captchaService) {
+ this.captchaService = captchaService;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ if (this.captchaService == null) {
+ throw new IllegalArgumentException(
+ "CaptchaServiceProxy must be defined ");
+ }
+ }
+
+ /**
+ * Does nothing. We use IoC container lifecycle services instead.
+ */
+ public void destroy() {
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+
+ if ((request != null)
+ && (request
+ .getParameter(CAPTCHA_VALIDATION_SECURITY_PARAMETER_KEY) != null)) {
+ logger.debug("captcha validation parameter not found, do nothing");
+ // validate the request against CaptchaServiceProxy
+ boolean valid = false;
+
+ logger.debug("try to validate");
+ valid = this.captchaService.validateRequest(request);
+ logger.debug("captchaServiceProxy says : request is valid ="
+ + valid);
+ if (valid) {
+ logger.debug("update the context");
+ ((CaptchaSecurityContext) SecurityContextHolder.getContext())
+ .setHuman();
+
+ }
+
+ } else {
+ logger.debug("captcha validation parameter not found, do nothing");
+ }
+ logger.debug("chain...");
+ chain.doFilter(request, response);
+ }
+
+ /**
+ * Does nothing. We use IoC container lifecycle services instead.
+ *
+ * @param filterConfig
+ * ignored
+ *
+ * @throws ServletException
+ * ignored
+ */
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTests.java b/core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTests.java
new file mode 100644
index 0000000000..ad28533e5b
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/captcha/CaptchaChannelProcessorTests.java
@@ -0,0 +1,536 @@
+/* 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 javax.servlet.ServletException;
+
+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;
+
+/**
+ * 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");
+ 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");
+ 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");
+ 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");
+ 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());
+
+ Thread.sleep(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());
+
+ Thread.sleep(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());
+
+ Thread.sleep(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");
+ 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());
+
+ Thread.sleep(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());
+
+ Thread.sleep(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")));
+ }
+
+}
diff --git a/core/src/test/java/org/acegisecurity/captcha/CaptchaEntryPointTests.java b/core/src/test/java/org/acegisecurity/captcha/CaptchaEntryPointTests.java
new file mode 100644
index 0000000000..94d134c9d7
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/captcha/CaptchaEntryPointTests.java
@@ -0,0 +1,385 @@
+/* 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.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import net.sf.acegisecurity.MockPortResolver;
+import net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint;
+import net.sf.acegisecurity.util.PortMapperImpl;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+/**
+ * Tests {@link RetryWithHttpEntryPoint}.
+ *
+ * @author Ben Alex
+ * @version $Id: RetryWithHttpEntryPointTests.java,v 1.4 2005/04/11 01:07:02
+ * luke_t Exp $
+ */
+public class CaptchaEntryPointTests extends TestCase {
+ // ~ Methods
+ // ================================================================
+
+ public final void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(CaptchaEntryPointTests.class);
+ }
+
+ public void testDetectsMissingCaptchaFormUrl() throws Exception {
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setPortResolver(new MockPortResolver(80, 443));
+
+ try {
+ ep.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("captchaFormUrl must be specified", expected
+ .getMessage());
+ }
+ }
+
+ public void testDetectsMissingPortMapper() throws Exception {
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("xxx");
+ ep.setPortMapper(null);
+
+ try {
+ ep.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("portMapper must be specified", expected.getMessage());
+ }
+ }
+
+ public void testDetectsMissingPortResolver() throws Exception {
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("xxx");
+ ep.setPortResolver(null);
+
+ try {
+ ep.afterPropertiesSet();
+ fail("Should have thrown IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("portResolver must be specified", expected
+ .getMessage());
+ }
+
+ }
+
+ public void testGettersSetters() {
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("/hello");
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setPortResolver(new MockPortResolver(8080, 8443));
+ assertEquals("/hello", ep.getCaptchaFormUrl());
+ assertTrue(ep.getPortMapper() != null);
+ assertTrue(ep.getPortResolver() != null);
+
+ assertEquals("originalRequest", ep.getOriginalRequestParameterName());
+ ep.setOriginalRequestParameterName("Z");
+ assertEquals("Z", ep.getOriginalRequestParameterName());
+
+ assertEquals(false, ep.isIncludeOriginalRequest());
+ ep.setIncludeOriginalRequest(true);
+ assertEquals(true, ep.isIncludeOriginalRequest());
+
+ assertEquals(false, ep.isOutsideWebApp());
+ ep.setOutsideWebApp(true);
+ assertEquals(true, ep.isOutsideWebApp());
+
+ ep.setForceHttps(false);
+ assertFalse(ep.getForceHttps());
+ ep.setForceHttps(true);
+ assertTrue(ep.getForceHttps());
+
+ }
+
+ public void testHttpsOperationFromOriginalHttpUrl() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURI("/some_path");
+ request.setScheme("http");
+ request.setServerName("www.example.com");
+ request.setContextPath("/bigWebApp");
+ request.setServerPort(80);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("/hello");
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setForceHttps(true);
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setPortResolver(new MockPortResolver(80, 443));
+ ep.afterPropertiesSet();
+
+ ep.commence(request, response);
+ 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());
+
+ // 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());
+
+ PortMapperImpl portMapper = new PortMapperImpl();
+ Map map = new HashMap();
+ map.put("8888", "9999");
+ portMapper.setPortMappings(map);
+ response = new MockHttpServletResponse();
+
+ ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("/hello");
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setForceHttps(true);
+ ep.setPortMapper(portMapper);
+ ep.setPortResolver(new MockPortResolver(8888, 9999));
+ ep.afterPropertiesSet();
+
+ ep.commence(request, response);
+ assertEquals("https://www.example.com:9999/bigWebApp/hello", response
+ .getRedirectedUrl());
+ }
+
+ public void testHttpsOperationFromOriginalHttpsUrl() throws Exception {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURI("/some_path");
+ request.setScheme("https");
+ request.setServerName("www.example.com");
+ request.setContextPath("/bigWebApp");
+ request.setServerPort(443);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("/hello");
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setForceHttps(true);
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setPortResolver(new MockPortResolver(80, 443));
+ ep.afterPropertiesSet();
+
+ ep.commence(request, response);
+ 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());
+ }
+
+ public void testNormalOperation() throws Exception {
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("/hello");
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setPortResolver(new MockPortResolver(80, 443));
+ ep.afterPropertiesSet();
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURI("/some_path");
+ request.setContextPath("/bigWebApp");
+ request.setScheme("http");
+ request.setServerName("www.example.com");
+ request.setContextPath("/bigWebApp");
+ request.setServerPort(80);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ ep.afterPropertiesSet();
+ ep.commence(request, response);
+ assertEquals("http://www.example.com/bigWebApp/hello", response
+ .getRedirectedUrl());
+ }
+
+ public void testOperationWhenHttpsRequestsButHttpsPortUnknown()
+ throws Exception {
+ CaptchaEntryPoint ep = new CaptchaEntryPoint();
+ ep.setCaptchaFormUrl("/hello");
+ ep.setPortMapper(new PortMapperImpl());
+ ep.setPortResolver(new MockPortResolver(8888, 1234));
+ ep.setForceHttps(true);
+ ep.afterPropertiesSet();
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setRequestURI("/some_path");
+ request.setContextPath("/bigWebApp");
+ request.setScheme("http");
+ request.setServerName("www.example.com");
+ request.setContextPath("/bigWebApp");
+ request.setServerPort(8888); // NB: Port we can't resolve
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ ep.afterPropertiesSet();
+ ep.commence(request, response);
+
+ // 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());
+ }
+
+ 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));
+ ep.setPortMapper(mapper);
+
+ ep.setPortResolver(new MockPortResolver(8888, 1234));
+ ep.setIncludeOriginalRequest(true);
+ ep.afterPropertiesSet();
+
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ 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?originalRequest=http://www.example.com:8888/some_path",
+ response.getRedirectedUrl());
+
+ // test the query params
+ request.addParameter("name", "value");
+ response = new MockHttpServletResponse();
+ ep.commence(request, response);
+ assertEquals(
+ "http://www.example.com:8888/hello?originalRequest=http://www.example.com:8888/some_path?name=value",
+ response.getRedirectedUrl());
+
+ // test the multiple query params
+ request.addParameter("name", "value");
+ request.addParameter("name1", "value2");
+ response = new MockHttpServletResponse();
+ ep.commence(request, response);
+ assertEquals(
+ "http://www.example.com:8888/hello?originalRequest=http://www.example.com:8888/some_path?name=value&name1=value2",
+ 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&originalRequest=http://www.example.com:8888/some_path?name=value&name1=value2",
+ response.getRedirectedUrl());
+
+ // with forcing!!!
+ ep.setForceHttps(true);
+ response = new MockHttpServletResponse();
+ ep.commence(request, response);
+ assertEquals(
+ "https://www.example.com:1234/hello?toto=titi&originalRequest=http://www.example.com:8888/some_path?name=value&name1=value2",
+ 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));
+ ep.setPortMapper(mapper);
+
+ ep.setPortResolver(new MockPortResolver(8888, 1234));
+ ep.setIncludeOriginalRequest(true);
+ ep.setOutsideWebApp(true);
+
+ ep.afterPropertiesSet();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ 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(
+ "https://www.jcaptcha.net/dotest/?originalRequest=http://www.example.com:8888/some_path",
+ response.getRedirectedUrl());
+
+ // test the query params
+ request.addParameter("name", "value");
+ response = new MockHttpServletResponse();
+ ep.commence(request, response);
+ assertEquals(
+ "https://www.jcaptcha.net/dotest/?originalRequest=http://www.example.com:8888/some_path?name=value",
+ response.getRedirectedUrl());
+
+ // test the multiple query params
+ request.addParameter("name", "value");
+ request.addParameter("name1", "value2");
+ response = new MockHttpServletResponse();
+ ep.commence(request, response);
+ assertEquals(
+ "https://www.jcaptcha.net/dotest/?originalRequest=http://www.example.com:8888/some_path?name=value&name1=value2",
+ 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&originalRequest=http://www.example.com:8888/some_path?name=value&name1=value2",
+ response.getRedirectedUrl());
+
+ // with forcing!!!
+ ep.setForceHttps(true);
+ response = new MockHttpServletResponse();
+ ep.commence(request, response);
+ assertEquals(
+ "https://www.jcaptcha.net/dotest/?toto=titi&originalRequest=http://www.example.com:8888/some_path?name=value&name1=value2",
+ response.getRedirectedUrl());
+
+ }
+
+}
diff --git a/core/src/test/java/org/acegisecurity/captcha/CaptchaSecurityContextImplTests.java b/core/src/test/java/org/acegisecurity/captcha/CaptchaSecurityContextImplTests.java
new file mode 100644
index 0000000000..e554b5aa3e
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/captcha/CaptchaSecurityContextImplTests.java
@@ -0,0 +1,64 @@
+package net.sf.acegisecurity.captcha;
+
+import net.sf.acegisecurity.context.SecurityContextImplTests;
+
+public class CaptchaSecurityContextImplTests extends SecurityContextImplTests {
+
+ 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());
+ }
+
+ public void testIncrementRequests() {
+ CaptchaSecurityContext context = new CaptchaSecurityContextImpl();
+ context.setHuman();
+ assertEquals("should be human", true, context.isHuman());
+ assertEquals("should be 0", 0, context
+ .getHumanRestrictedResourcesRequestsCount());
+ context.incrementHumanRestrictedRessoucesRequestsCount();
+ 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());
+ context.incrementHumanRestrictedRessoucesRequestsCount();
+ 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);
+
+ }
+
+}
diff --git a/core/src/test/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilterTests.java
new file mode 100644
index 0000000000..676afc153a
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/captcha/CaptchaValidationProcessingFilterTests.java
@@ -0,0 +1,83 @@
+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;
+
+public class CaptchaValidationProcessingFilterTests extends TestCase {
+
+ /*
+ */
+ public void testAfterPropertiesSet() throws Exception {
+ CaptchaValidationProcessingFilter filter = new CaptchaValidationProcessingFilter();
+
+ try {
+ filter.afterPropertiesSet();
+ fail("should have thrown an invalid argument exception");
+ } catch (Exception e) {
+ assertTrue("should be an InvalidArgumentException",
+ IllegalArgumentException.class.isAssignableFrom(e
+ .getClass()));
+ }
+ filter.setCaptchaService(new MockCaptchaServiceProxy());
+ filter.afterPropertiesSet();
+
+ }
+
+ /*
+ * 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();
+ 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);
+ 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());
+
+ }
+
+ /*
+ * 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();
+ request
+ .addParameter(
+ CaptchaValidationProcessingFilter.CAPTCHA_VALIDATION_SECURITY_PARAMETER_KEY,
+ "");
+
+ CaptchaValidationProcessingFilter filter = new CaptchaValidationProcessingFilter();
+ 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("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());
+
+ }
+
+}
diff --git a/core/src/test/java/org/acegisecurity/captcha/MockCaptchaServiceProxy.java b/core/src/test/java/org/acegisecurity/captcha/MockCaptchaServiceProxy.java
new file mode 100644
index 0000000000..22df451797
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/captcha/MockCaptchaServiceProxy.java
@@ -0,0 +1,32 @@
+/* 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 javax.servlet.ServletRequest;
+
+public class MockCaptchaServiceProxy implements CaptchaServiceProxy {
+
+ public boolean valid = false;
+
+ public boolean hasBeenCalled = false;
+
+ public boolean validateRequest(ServletRequest request) {
+ hasBeenCalled = true;
+ return valid;
+
+ }
+
+}