diff --git a/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java b/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java index 0ed9cef42a..7a6005331e 100644 --- a/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java +++ b/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java @@ -18,6 +18,7 @@ package net.sf.acegisecurity.intercept.web; import net.sf.acegisecurity.AccessDeniedException; import net.sf.acegisecurity.AuthenticationException; import net.sf.acegisecurity.ui.AbstractProcessingFilter; +import net.sf.acegisecurity.util.PortResolver; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -74,6 +75,10 @@ import javax.servlet.http.HttpServletResponse; * AuthenticationException is detected. Note that this may also * switch the current protocol from http to https for a an SSL login. * + *
  • + * portResolver is used to determine the "real" port that a + * request was received on. + *
  • * * *

    @@ -95,6 +100,7 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean { private AuthenticationEntryPoint authenticationEntryPoint; private FilterSecurityInterceptor filterSecurityInterceptor; + private PortResolver portResolver; //~ Methods ================================================================ @@ -116,6 +122,14 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean { return filterSecurityInterceptor; } + public void setPortResolver(PortResolver portResolver) { + this.portResolver = portResolver; + } + + public PortResolver getPortResolver() { + return portResolver; + } + public void afterPropertiesSet() throws Exception { if (authenticationEntryPoint == null) { throw new IllegalArgumentException( @@ -126,6 +140,10 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean { throw new IllegalArgumentException( "filterSecurityInterceptor must be specified"); } + + if (portResolver == null) { + throw new IllegalArgumentException("portResolver must be specified"); + } } public void destroy() {} @@ -151,14 +169,31 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean { } catch (AuthenticationException authentication) { HttpServletRequest httpRequest = (HttpServletRequest) request; + int port = portResolver.getServerPort(request); + boolean includePort = true; + + if ("http".equals(request.getScheme().toLowerCase()) + && (port == 80)) { + includePort = false; + } + + if ("https".equals(request.getScheme().toLowerCase()) + && (port == 443)) { + includePort = false; + } + + String targetUrl = request.getScheme() + "://" + + request.getServerName() + ((includePort) ? (":" + port) : "") + + httpRequest.getContextPath() + fi.getRequestUrl(); + if (logger.isDebugEnabled()) { logger.debug( "Authentication failed - adding target URL to Session: " - + fi.getFullRequestUrl()); + + targetUrl); } ((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY, - fi.getFullRequestUrl()); + targetUrl); authenticationEntryPoint.commence(request, response); } catch (AccessDeniedException accessDenied) { if (logger.isDebugEnabled()) { diff --git a/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPoint.java b/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPoint.java index 21133b8b76..14dfb791c8 100644 --- a/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPoint.java +++ b/core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPoint.java @@ -16,15 +16,16 @@ package net.sf.acegisecurity.ui.webapp; import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint; +import net.sf.acegisecurity.util.PortMapper; +import net.sf.acegisecurity.util.PortResolver; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -43,20 +44,11 @@ import javax.servlet.http.HttpServletResponse; *

    * By setting the forceHttps property to true, you may configure the * class to force the protocol used for the login form to be - * https, even if the original intercepted request for a resource - * used the http protocol. When this happens, after a successful - * login (via https), the original resource will still be accessed as http, - * via the original request URL. For the forced https feature to work, the - * class must have a valid mapping from an http port in the original request - * to an https port for the login page (the same server name will be used, - * only the scheme and port will be changed). By default, http requests to - * port 80 will be mapped to login page https requests on port 443 (standard - * https port), and port 8080 will be mapped to port 8443. These mappings may - * be customized by setting the httpsPortMappings property. Any - * intercepted http request on a port which does not have a mapping will - * result in the protocol remaining as http. Any intercepted request which is - * already https will always result in the login page being accessed as https, - * regardless of the state of the forceHttps property. + * HTTPS, even if the original intercepted request for a resource + * used the HTTP protocol. When this happens, after a successful + * login (via HTTPS), the original resource will still be accessed as HTTP, + * via the original request URL. For the forced HTTPS feature to work, the + * {@link PortMapper} is consulted to determine the HTTP:HTTPS pairs. *

    * * @author Ben Alex @@ -65,20 +57,17 @@ import javax.servlet.http.HttpServletResponse; */ public class AuthenticationProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(AuthenticationProcessingFilterEntryPoint.class); + //~ Instance fields ======================================================== - private HashMap httpsPortMappings; + private PortMapper portMapper; + private PortResolver portResolver; private String loginFormUrl; private boolean forceHttps = false; - //~ Constructors =========================================================== - - public AuthenticationProcessingFilterEntryPoint() { - httpsPortMappings = new HashMap(); - httpsPortMappings.put(new Integer(80), new Integer(443)); - httpsPortMappings.put(new Integer(8080), new Integer(8443)); - } - //~ Methods ================================================================ /** @@ -88,8 +77,6 @@ public class AuthenticationProcessingFilterEntryPoint * https, then * * @param forceHttps - * - * @todo Generated comment */ public void setForceHttps(boolean forceHttps) { this.forceHttps = forceHttps; @@ -99,56 +86,6 @@ public class AuthenticationProcessingFilterEntryPoint return forceHttps; } - /** - *

    - * Set to override the default http port to https port mappings of 80:443, - * and 8080:8443. - *

    - * In a Spring XML ApplicationContext, a definition would look something - * like this: - *
    -     *   <property name="httpsPortMapping">
    -     *     <map>
    -     *       <entry key="80"><value>443</value></entry>
    -     *       <entry key="8080"><value>8443</value></entry>
    -     *     </map>
    -     *   </property>
    -     * 
    - * - * @param newMappings A Map consisting of String keys and String values, - * where for each entry the key is the string representation of an - * integer http port number, and the value is the string - * representation of the corresponding integer https port number. - * - * @throws IllegalArgumentException if input map does not consist of String - * keys and values, each representing an integer port number in - * the range 1-65535 for that mapping. - */ - public void setHttpsPortMappings(HashMap newMappings) { - httpsPortMappings.clear(); - - Iterator it = newMappings.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - Integer httpPort = new Integer((String) entry.getKey()); - Integer httpsPort = new Integer((String) entry.getValue()); - - if ((httpPort.intValue() < 1) || (httpPort.intValue() > 65535) - || (httpsPort.intValue() < 1) || (httpsPort.intValue() > 65535)) { - throw new IllegalArgumentException( - "one or both ports out of legal range: " + httpPort + ", " - + httpsPort); - } - - httpsPortMappings.put(httpPort, httpsPort); - - if (httpsPortMappings.size() < 1) { - throw new IllegalArgumentException("must map at least one port"); - } - } - } - /** * The URL where the AuthenticationProcessingFilter login page * can be found. Should be relative to the web-app context path, and @@ -164,40 +101,79 @@ public class AuthenticationProcessingFilterEntryPoint return loginFormUrl; } + 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 void afterPropertiesSet() throws Exception { if ((loginFormUrl == null) || "".equals(loginFormUrl)) { throw new IllegalArgumentException("loginFormUrl must be specified"); } + + if (portMapper == null) { + throw new IllegalArgumentException("portMapper must be specified"); + } + + if (portResolver == null) { + throw new IllegalArgumentException("portResolver must be specified"); + } } public void commence(ServletRequest request, ServletResponse response) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; + String scheme = request.getScheme(); + String serverName = request.getServerName(); + int serverPort = portResolver.getServerPort(request); String contextPath = req.getContextPath(); - String redirectUrl = contextPath + loginFormUrl; + boolean includePort = true; + + if ("http".equals(scheme.toLowerCase()) && (serverPort == 80)) { + includePort = false; + } + + if ("https".equals(scheme.toLowerCase()) && (serverPort == 443)) { + includePort = false; + } + + String redirectUrl = scheme + "://" + serverName + + ((includePort) ? (":" + serverPort) : "") + contextPath + + loginFormUrl; if (forceHttps && req.getScheme().equals("http")) { - Integer httpPort = new Integer(req.getServerPort()); - Integer httpsPort = (Integer) httpsPortMappings.get(httpPort); + Integer httpPort = new Integer(portResolver.getServerPort(request)); + Integer httpsPort = (Integer) portMapper.lookupHttpsPort(httpPort); if (httpsPort != null) { - String serverName = req.getServerName(); - redirectUrl = "https://" + serverName + ":" + httpsPort - + contextPath + loginFormUrl; + if (httpsPort.intValue() == 443) { + includePort = false; + } else { + includePort = true; + } + + redirectUrl = "https://" + serverName + + ((includePort) ? (":" + httpsPort) : "") + contextPath + + loginFormUrl; } } + if (logger.isDebugEnabled()) { + logger.debug("Redirecting to: " + redirectUrl); + } + ((HttpServletResponse) response).sendRedirect(redirectUrl); } - - /** - * Returns the translated (Integer -> Integer) version of the original port - * mapping specified via setHttpsPortMapping() - * - * @return DOCUMENT ME! - */ - protected HashMap getTranslatedHttpsPortMappings() { - return httpsPortMappings; - } } diff --git a/core/src/main/java/org/acegisecurity/util/PortMapper.java b/core/src/main/java/org/acegisecurity/util/PortMapper.java new file mode 100644 index 0000000000..8a5d8349c5 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/util/PortMapper.java @@ -0,0 +1,54 @@ +/* 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.util; + +/** + * PortMapper implementations provide callers with information + * about which HTTP ports are associated with which HTTPS ports on the system, + * and vice versa. + * + * @author Ben Alex + * @version $Id$ + */ +public interface PortMapper { + //~ Methods ================================================================ + + /** + * Locates the HTTP port associated with the specified HTTPS port. + * + *

    + * Returns null if unknown. + *

    + * + * @param httpsPort + * + * @return the HTTP port or null if unknown + */ + public Integer lookupHttpPort(Integer httpsPort); + + /** + * Locates the HTTPS port associated with the specified HTTP port. + * + *

    + * Returns null if unknown. + *

    + * + * @param httpPort + * + * @return the HTTPS port or null if unknown + */ + public Integer lookupHttpsPort(Integer httpPort); +} diff --git a/core/src/main/java/org/acegisecurity/util/PortMapperImpl.java b/core/src/main/java/org/acegisecurity/util/PortMapperImpl.java new file mode 100644 index 0000000000..2bd699f350 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/util/PortMapperImpl.java @@ -0,0 +1,134 @@ +/* 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.util; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + + +/** + * Concrete implementation of {@link PortMapper} that obtains HTTP:HTTPS pairs + * from the application context. + * + *

    + * By default the implementation will assume 80:443 and 8080:8443 are + * HTTP:HTTPS pairs respectively. If different pairs are required, use {@link + * #setPortMappings(Map)}. + *

    + * + * @author Ben Alex + * @author colin sampaleanu + * @version $Id$ + */ +public class PortMapperImpl implements PortMapper { + //~ Instance fields ======================================================== + + private Map httpsPortMappings; + + //~ Constructors =========================================================== + + public PortMapperImpl() { + httpsPortMappings = new HashMap(); + httpsPortMappings.put(new Integer(80), new Integer(443)); + httpsPortMappings.put(new Integer(8080), new Integer(8443)); + } + + //~ Methods ================================================================ + + /** + *

    + * Set to override the default HTTP port to HTTPS port mappings of 80:443, + * and 8080:8443. + *

    + * In a Spring XML ApplicationContext, a definition would look something + * like this: + *
    +     *   <property name="httpsPortMapping">
    +     *     <map>
    +     *       <entry key="80"><value>443</value></entry>
    +     *       <entry key="8080"><value>8443</value></entry>
    +     *     </map>
    +     *   </property>
    +     * 
    + * + * @param newMappings A Map consisting of String keys and String values, + * where for each entry the key is the string representation of an + * integer HTTP port number, and the value is the string + * representation of the corresponding integer HTTPS port number. + * + * @throws IllegalArgumentException if input map does not consist of String + * keys and values, each representing an integer port number in + * the range 1-65535 for that mapping. + */ + public void setPortMappings(Map newMappings) { + if (newMappings == null) { + throw new IllegalArgumentException( + "A valid list of HTTPS port mappings must be provided"); + } + + httpsPortMappings.clear(); + + Iterator it = newMappings.entrySet().iterator(); + + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + Integer httpPort = new Integer((String) entry.getKey()); + Integer httpsPort = new Integer((String) entry.getValue()); + + if ((httpPort.intValue() < 1) || (httpPort.intValue() > 65535) + || (httpsPort.intValue() < 1) || (httpsPort.intValue() > 65535)) { + throw new IllegalArgumentException( + "one or both ports out of legal range: " + httpPort + ", " + + httpsPort); + } + + httpsPortMappings.put(httpPort, httpsPort); + } + + if (httpsPortMappings.size() < 1) { + throw new IllegalArgumentException("must map at least one port"); + } + } + + /** + * Returns the translated (Integer -> Integer) version of the original port + * mapping specified via setHttpsPortMapping() + * + * @return DOCUMENT ME! + */ + public Map getTranslatedPortMappings() { + return httpsPortMappings; + } + + public Integer lookupHttpPort(Integer httpsPort) { + Iterator iter = httpsPortMappings.keySet().iterator(); + + while (iter.hasNext()) { + Integer httpPort = (Integer) iter.next(); + + if (httpsPortMappings.get(httpPort).equals(httpsPort)) { + return httpPort; + } + } + + return null; + } + + public Integer lookupHttpsPort(Integer httpPort) { + return (Integer) httpsPortMappings.get(httpPort); + } +} diff --git a/core/src/main/java/org/acegisecurity/util/PortResolver.java b/core/src/main/java/org/acegisecurity/util/PortResolver.java new file mode 100644 index 0000000000..92ca9cc40e --- /dev/null +++ b/core/src/main/java/org/acegisecurity/util/PortResolver.java @@ -0,0 +1,46 @@ +/* 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.util; + +import javax.servlet.ServletRequest; + + +/** + * A PortResolver determines the port a web request was received + * on. + * + *

    + * This interface is necessary because + * ServletRequest.getServerPort() may not return the correct port + * in certain circumstances. For example, if the browser does not construct + * the URL correctly after a redirect. + *

    + * + * @author Ben Alex + * @version $Id$ + */ +public interface PortResolver { + //~ Methods ================================================================ + + /** + * Indicates the port the ServletRequest was received on. + * + * @param request that the method should lookup the port for + * + * @return the port the request was received on + */ + public int getServerPort(ServletRequest request); +} diff --git a/core/src/main/java/org/acegisecurity/util/PortResolverImpl.java b/core/src/main/java/org/acegisecurity/util/PortResolverImpl.java new file mode 100644 index 0000000000..c886963e03 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/util/PortResolverImpl.java @@ -0,0 +1,92 @@ +/* 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.util; + +import org.springframework.beans.factory.InitializingBean; + +import javax.servlet.ServletRequest; + + +/** + * Concrete implementation of {@link PortResolver} that obtains the port from + * ServletRequest.getServerPort(). + * + *

    + * If either the alwaysHttpPort or alwaysHttpsPort + * properties are set, these ports will be used instead of those + * obtained from the ServletRequest.getServerPort() method. + * Setting these properties will cause the + * ServletRequest.getScheme() method to be used to determine + * whether a request was HTTP or HTTPS, and then return the port defined by + * the always[Scheme]Port property. You can configure zero, one + * or both of these properties. + *

    + * + * @author Ben Alex + * @version $Id$ + */ +public class PortResolverImpl implements InitializingBean, PortResolver { + //~ Instance fields ======================================================== + + private int alwaysHttpPort = 0; + private int alwaysHttpsPort = 0; + + //~ Methods ================================================================ + + public void setAlwaysHttpPort(int alwaysHttpPort) { + this.alwaysHttpPort = alwaysHttpPort; + } + + public int getAlwaysHttpPort() { + return alwaysHttpPort; + } + + public void setAlwaysHttpsPort(int alwaysHttpsPort) { + this.alwaysHttpsPort = alwaysHttpsPort; + } + + public int getAlwaysHttpsPort() { + return alwaysHttpsPort; + } + + public int getServerPort(ServletRequest request) { + if ("http".equals(request.getScheme().toLowerCase()) + && (alwaysHttpPort != 0)) { + return alwaysHttpPort; + } + + if ("https".equals(request.getScheme().toLowerCase()) + && (alwaysHttpsPort != 0)) { + return alwaysHttpsPort; + } + + return request.getServerPort(); + } + + public void afterPropertiesSet() throws Exception { + if ((alwaysHttpPort != 0) + && ((alwaysHttpPort > 65535) || (alwaysHttpPort < 0))) { + throw new IllegalArgumentException( + "alwaysHttpPort must be between 1 and 65535"); + } + + if ((alwaysHttpsPort != 0) + && ((alwaysHttpsPort > 65535) || (alwaysHttpsPort < 0))) { + throw new IllegalArgumentException( + "alwaysHttpsPort must be between 1 and 65535"); + } + } +} diff --git a/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java b/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java index 009a7d93ed..f5874c4e43 100644 --- a/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java +++ b/core/src/test/java/org/acegisecurity/MockHttpServletRequest.java @@ -53,13 +53,13 @@ public class MockHttpServletRequest implements HttpServletRequest { private Map paramMap = new HashMap(); private Principal principal; private String contextPath = ""; + private String pathInfo; // null for no extra path private String queryString = null; private String requestURL; private String scheme; private String serverName; private String servletPath; private int serverPort; - private String pathInfo; // null for no extra path //~ Constructors =========================================================== @@ -197,11 +197,10 @@ public class MockHttpServletRequest implements HttpServletRequest { throw new UnsupportedOperationException("mock method not implemented"); } - public void setPathInfo(String pathInfo) { this.pathInfo = pathInfo; } - + public String getPathInfo() { return pathInfo; } @@ -283,7 +282,11 @@ public class MockHttpServletRequest implements HttpServletRequest { } public boolean isSecure() { - throw new UnsupportedOperationException("mock method not implemented"); + if ("https".equals(scheme)) { + return true; + } else { + return false; + } } public void setServerName(String serverName) { diff --git a/core/src/test/java/org/acegisecurity/MockPortResolver.java b/core/src/test/java/org/acegisecurity/MockPortResolver.java new file mode 100644 index 0000000000..e22bf73a4a --- /dev/null +++ b/core/src/test/java/org/acegisecurity/MockPortResolver.java @@ -0,0 +1,54 @@ +/* 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; + +import net.sf.acegisecurity.util.PortResolver; + +import javax.servlet.ServletRequest; + + +/** + * Always returns the constructor-specified HTTP and HTTPS ports. + * + * @author Ben Alex + * @version $Id$ + */ +public class MockPortResolver implements PortResolver { + //~ Instance fields ======================================================== + + private int http = 80; + private int https = 443; + + //~ Constructors =========================================================== + + public MockPortResolver(int http, int https) { + this.http = http; + this.https = https; + } + + private MockPortResolver() {} + + //~ Methods ================================================================ + + public int getServerPort(ServletRequest request) { + if ((request.getScheme() != null) + && request.getScheme().equals("https")) { + return https; + } else { + return http; + } + } +} diff --git a/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java b/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java index 20c5e4010b..72706f305c 100644 --- a/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java +++ b/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java @@ -23,6 +23,7 @@ import net.sf.acegisecurity.MockAuthenticationEntryPoint; import net.sf.acegisecurity.MockHttpServletRequest; import net.sf.acegisecurity.MockHttpServletResponse; import net.sf.acegisecurity.MockHttpSession; +import net.sf.acegisecurity.MockPortResolver; import net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter; import java.io.IOException; @@ -120,6 +121,9 @@ public class SecurityEnforcementFilterTests extends TestCase { filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint( "/login.jsp")); assertTrue(filter.getAuthenticationEntryPoint() != null); + + filter.setPortResolver(new MockPortResolver(80, 443)); + assertTrue(filter.getPortResolver() != null); } public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthenticationException() @@ -128,6 +132,10 @@ public class SecurityEnforcementFilterTests extends TestCase { MockHttpServletRequest request = new MockHttpServletRequest(null, new MockHttpSession()); request.setServletPath("/secure/page.html"); + request.setServerPort(80); + request.setScheme("http"); + request.setServerName("www.example.com"); + request.setContextPath("/mycontext"); request.setRequestURL( "http://www.example.com/mycontext/secure/page.html"); @@ -143,15 +151,51 @@ public class SecurityEnforcementFilterTests extends TestCase { filter.setFilterSecurityInterceptor(interceptor); filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint( "/login.jsp")); + filter.setPortResolver(new MockPortResolver(80, 443)); filter.afterPropertiesSet(); MockHttpServletResponse response = new MockHttpServletResponse(); filter.doFilter(request, response, chain); - assertEquals("/login.jsp", response.getRedirect()); + assertEquals("/mycontext/login.jsp", response.getRedirect()); assertEquals("http://www.example.com/mycontext/secure/page.html", request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY)); } + public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWithExoticPortWhenAuthenticationException() + throws Exception { + // Setup our HTTP request + MockHttpServletRequest request = new MockHttpServletRequest(null, + new MockHttpSession()); + request.setServletPath("/secure/page.html"); + request.setServerPort(8080); + request.setScheme("http"); + request.setServerName("www.example.com"); + request.setContextPath("/mycontext"); + request.setRequestURL( + "http://www.example.com:8080/mycontext/secure/page.html"); + + // Setup our expectation that the filter chain will not be invoked, as access is denied + MockFilterChain chain = new MockFilterChain(false); + + // Setup the FilterSecurityInterceptor thrown an authentication failure exceptions + MockFilterSecurityInterceptor interceptor = new MockFilterSecurityInterceptor(false, + true); + + // Test + SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); + filter.setFilterSecurityInterceptor(interceptor); + filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint( + "/login.jsp")); + filter.setPortResolver(new MockPortResolver(8080, 8443)); + filter.afterPropertiesSet(); + + MockHttpServletResponse response = new MockHttpServletResponse(); + filter.doFilter(request, response, chain); + assertEquals("/mycontext/login.jsp", response.getRedirect()); + assertEquals("http://www.example.com:8080/mycontext/secure/page.html", + request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY)); + } + public void testStartupDetectsMissingAuthenticationEntryPoint() throws Exception { SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); @@ -182,6 +226,22 @@ public class SecurityEnforcementFilterTests extends TestCase { } } + public void testStartupDetectsMissingPortResolver() + throws Exception { + SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); + filter.setFilterSecurityInterceptor(new MockFilterSecurityInterceptor( + false, false)); + filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint( + "/login.jsp")); + + try { + filter.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("portResolver must be specified", expected.getMessage()); + } + } + public void testSuccessfulAccessGrant() throws Exception { // Setup our HTTP request MockHttpServletRequest request = new MockHttpServletRequest(null, diff --git a/core/src/test/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPointTests.java b/core/src/test/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPointTests.java index ba1f33dd4c..b7ed9b5f6e 100644 --- a/core/src/test/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPointTests.java +++ b/core/src/test/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPointTests.java @@ -19,8 +19,11 @@ import junit.framework.TestCase; import net.sf.acegisecurity.MockHttpServletRequest; import net.sf.acegisecurity.MockHttpServletResponse; +import net.sf.acegisecurity.MockPortResolver; +import net.sf.acegisecurity.util.PortMapperImpl; import java.util.HashMap; +import java.util.Map; /** @@ -43,6 +46,8 @@ public class AuthenticationProcessingFilterEntryPointTests extends TestCase { public void testDetectsMissingLoginFormUrl() throws Exception { AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(80, 443)); try { ep.afterPropertiesSet(); @@ -52,10 +57,45 @@ public class AuthenticationProcessingFilterEntryPointTests extends TestCase { } } + public void testDetectsMissingPortMapper() throws Exception { + AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); + ep.setLoginFormUrl("xxx"); + ep.setPortResolver(new MockPortResolver(80, 443)); + + try { + ep.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("portMapper must be specified", expected.getMessage()); + } + } + + public void testDetectsMissingPortResolver() throws Exception { + AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); + ep.setLoginFormUrl("xxx"); + ep.setPortMapper(new PortMapperImpl()); + + try { + ep.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("portResolver must be specified", expected.getMessage()); + } + } + public void testGettersSetters() { AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); ep.setLoginFormUrl("/hello"); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(8080, 8443)); assertEquals("/hello", ep.getLoginFormUrl()); + assertTrue(ep.getPortMapper() != null); + assertTrue(ep.getPortResolver() != null); + + ep.setForceHttps(false); + assertFalse(ep.getForceHttps()); + ep.setForceHttps(true); + assertTrue(ep.getForceHttps()); } public void testHttpsOperation() throws Exception { @@ -70,30 +110,40 @@ public class AuthenticationProcessingFilterEntryPointTests extends TestCase { AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); ep.setLoginFormUrl("/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:443/bigWebApp/hello", + assertEquals("https://www.example.com/bigWebApp/hello", response.getRedirect()); request.setServerPort(8080); + ep.setPortResolver(new MockPortResolver(8080, 8443)); + ep.commence(request, response); + System.out.println(response.getRedirect()); + assertEquals("https://www.example.com:8443/bigWebApp/hello", + response.getRedirect()); + + // Now test an unusual custom HTTP:HTTPS is handled properly + request.setServerPort(8888); ep.commence(request, response); assertEquals("https://www.example.com:8443/bigWebApp/hello", response.getRedirect()); - // check that unknown port leaves things as-is - request.setServerPort(8888); - ep.commence(request, response); - assertEquals("/bigWebApp/hello", response.getRedirect()); + PortMapperImpl portMapper = new PortMapperImpl(); + Map map = new HashMap(); + map.put("8888", "9999"); + portMapper.setPortMappings(map); ep = new AuthenticationProcessingFilterEntryPoint(); ep.setLoginFormUrl("/hello"); + ep.setPortMapper(new PortMapperImpl()); ep.setForceHttps(true); - - HashMap map = new HashMap(); - map.put("8888", "9999"); - ep.setHttpsPortMappings(map); + ep.setPortMapper(portMapper); + ep.setPortResolver(new MockPortResolver(8888, 9999)); ep.afterPropertiesSet(); ep.commence(request, response); @@ -104,50 +154,50 @@ public class AuthenticationProcessingFilterEntryPointTests extends TestCase { public void testNormalOperation() throws Exception { AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); ep.setLoginFormUrl("/hello"); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(80, 443)); + ep.afterPropertiesSet(); MockHttpServletRequest request = new MockHttpServletRequest( "/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("/bigWebApp/hello", response.getRedirect()); + assertEquals("http://www.example.com/bigWebApp/hello", + response.getRedirect()); } - public void testSetSslPortMapping() { + public void testOperationWhenHttpsRequestsButHttpsPortUnknown() + throws Exception { AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint(); - HashMap map = new HashMap(); + ep.setLoginFormUrl("/hello"); + ep.setPortMapper(new PortMapperImpl()); + ep.setPortResolver(new MockPortResolver(8888, 1234)); + ep.setForceHttps(true); + ep.afterPropertiesSet(); - try { - ep.setHttpsPortMappings(map); - } catch (IllegalArgumentException expected) { - assertEquals("must map at least one port", expected.getMessage()); - } + MockHttpServletRequest request = new MockHttpServletRequest( + "/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 - map.put(new Integer(0).toString(), new Integer(443).toString()); + MockHttpServletResponse response = new MockHttpServletResponse(); - try { - ep.setHttpsPortMappings(map); - } catch (IllegalArgumentException expected) { - assertTrue(expected.getMessage().startsWith("one or both ports out of legal range")); - } + ep.afterPropertiesSet(); + ep.commence(request, response); - map.clear(); - map.put(new Integer(80).toString(), new Integer(100000).toString()); - - try { - ep.setHttpsPortMappings(map); - } catch (IllegalArgumentException expected) { - assertTrue(expected.getMessage().startsWith("one or both ports out of legal range")); - } - - map.clear(); - map.put(new Integer(80).toString(), new Integer(443).toString()); - ep.setHttpsPortMappings(map); - map = ep.getTranslatedHttpsPortMappings(); - assertTrue(map.size() == 1); - assertTrue(((Integer) map.get(new Integer(80))).equals(new Integer(443))); + // 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.getRedirect()); } } diff --git a/core/src/test/java/org/acegisecurity/util/PortMapperImplTests.java b/core/src/test/java/org/acegisecurity/util/PortMapperImplTests.java new file mode 100644 index 0000000000..048a09401c --- /dev/null +++ b/core/src/test/java/org/acegisecurity/util/PortMapperImplTests.java @@ -0,0 +1,120 @@ +/* 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.util; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Tests {@link PortMapperImpl}. + * + * @author Ben Alex + * @version $Id$ + */ +public class PortMapperImplTests extends TestCase { + //~ Constructors =========================================================== + + public PortMapperImplTests() { + super(); + } + + public PortMapperImplTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(PortMapperImplTests.class); + } + + public void testDefaultMappingsAreKnown() throws Exception { + PortMapperImpl portMapper = new PortMapperImpl(); + assertEquals(new Integer(80), + portMapper.lookupHttpPort(new Integer(443))); + assertEquals(new Integer(8080), + portMapper.lookupHttpPort(new Integer(8443))); + assertEquals(new Integer(443), + portMapper.lookupHttpsPort(new Integer(80))); + assertEquals(new Integer(8443), + portMapper.lookupHttpsPort(new Integer(8080))); + } + + public void testDetectsEmptyMap() throws Exception { + PortMapperImpl portMapper = new PortMapperImpl(); + + try { + portMapper.setPortMappings(new HashMap()); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testDetectsNullMap() throws Exception { + PortMapperImpl portMapper = new PortMapperImpl(); + + try { + portMapper.setPortMappings(null); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testGetTranslatedPortMappings() { + PortMapperImpl portMapper = new PortMapperImpl(); + assertEquals(2, portMapper.getTranslatedPortMappings().size()); + } + + public void testRejectsOutOfRangeMappings() { + PortMapperImpl portMapper = new PortMapperImpl(); + Map map = new HashMap(); + map.put("79", "80559"); + + try { + portMapper.setPortMappings(map); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + public void testReturnsNullIfHttpPortCannotBeFound() { + PortMapperImpl portMapper = new PortMapperImpl(); + assertTrue(portMapper.lookupHttpPort(new Integer("34343")) == null); + } + + public void testSupportsCustomMappings() { + PortMapperImpl portMapper = new PortMapperImpl(); + Map map = new HashMap(); + map.put("79", "442"); + + portMapper.setPortMappings(map); + + assertEquals(new Integer(79), + portMapper.lookupHttpPort(new Integer(442))); + assertEquals(new Integer(442), + portMapper.lookupHttpsPort(new Integer(79))); + } +} diff --git a/core/src/test/java/org/acegisecurity/util/PortResolverImplTests.java b/core/src/test/java/org/acegisecurity/util/PortResolverImplTests.java new file mode 100644 index 0000000000..b789b09429 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/util/PortResolverImplTests.java @@ -0,0 +1,144 @@ +/* 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.util; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.MockHttpServletRequest; + + +/** + * Tests {@link PortResolverImpl}. + * + * @author Ben Alex + * @version $Id$ + */ +public class PortResolverImplTests extends TestCase { + //~ Constructors =========================================================== + + public PortResolverImplTests() { + super(); + } + + public PortResolverImplTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(PortResolverImplTests.class); + } + + public void testGettersSetters() throws Exception { + PortResolverImpl pr = new PortResolverImpl(); + assertEquals(0, pr.getAlwaysHttpPort()); + assertEquals(0, pr.getAlwaysHttpsPort()); + + pr.setAlwaysHttpPort(80); + pr.setAlwaysHttpsPort(443); + assertEquals(80, pr.getAlwaysHttpPort()); + assertEquals(443, pr.getAlwaysHttpsPort()); + } + + public void testNormalOperation() throws Exception { + PortResolverImpl pr = new PortResolverImpl(); + pr.afterPropertiesSet(); + + MockHttpServletRequest request = new MockHttpServletRequest("X"); + request.setScheme("http"); + request.setServerPort(1021); + assertEquals(1021, pr.getServerPort(request)); + } + + public void testOverridesHttp() throws Exception { + PortResolverImpl pr = new PortResolverImpl(); + pr.setAlwaysHttpPort(495); + pr.afterPropertiesSet(); + + MockHttpServletRequest request = new MockHttpServletRequest("X"); + request.setServerPort(7676); + request.setScheme("HTtP"); // proves case insensitive handling + assertEquals(495, pr.getServerPort(request)); + + request.setScheme("https"); + assertEquals(7676, pr.getServerPort(request)); + } + + public void testOverridesHttps() throws Exception { + PortResolverImpl pr = new PortResolverImpl(); + pr.setAlwaysHttpsPort(987); + pr.afterPropertiesSet(); + + MockHttpServletRequest request = new MockHttpServletRequest("X"); + request.setServerPort(6949); + request.setScheme("HTtPs"); // proves case insensitive handling + assertEquals(987, pr.getServerPort(request)); + + request.setScheme("http"); + assertEquals(6949, pr.getServerPort(request)); + } + + public void testRejectsOutOfRangeHttp() throws Exception { + PortResolverImpl pr = new PortResolverImpl(); + pr.setAlwaysHttpPort(9999999); + + try { + pr.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("alwaysHttpPort must be between 1 and 65535", + expected.getMessage()); + } + + pr.setAlwaysHttpPort(-49); + + try { + pr.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("alwaysHttpPort must be between 1 and 65535", + expected.getMessage()); + } + } + + public void testRejectsOutOfRangeHttps() throws Exception { + PortResolverImpl pr = new PortResolverImpl(); + pr.setAlwaysHttpsPort(9999999); + + try { + pr.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("alwaysHttpsPort must be between 1 and 65535", + expected.getMessage()); + } + + pr.setAlwaysHttpsPort(-49); + + try { + pr.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("alwaysHttpsPort must be between 1 and 65535", + expected.getMessage()); + } + } +}