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

+ * The class responds to two case-sensitive keywords : + * + *

+ * + *

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

+ * + * + * @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; + + } + +}