Added two new default filters - 'port' and 'ssl' to ensure a request comes in on a certain port (with the 'ssl' filter being a PortFilter that just defaults to 443).

git-svn-id: https://svn.apache.org/repos/asf/incubator/jsecurity/trunk@760043 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/ki/util/StringUtils.java b/core/src/main/java/org/apache/ki/util/StringUtils.java
index 2c0f232..def3e1b 100644
--- a/core/src/main/java/org/apache/ki/util/StringUtils.java
+++ b/core/src/main/java/org/apache/ki/util/StringUtils.java
@@ -157,6 +157,39 @@
     }
 
     /**
+     * Returns the specified array as a comma-delimited (',') string.
+     * @param array the array whose contents will be converted to a string.
+     * @return the array's contents as a comma-delimited (',') string.
+     * @since 1.0
+     */
+    public static String toString(Object[] array) {
+        return toDelimitedString(array,",");
+    }
+
+    /**
+     * Returns the array's contents as a string, with each element delimited by the specified
+     * {@code delimiter} argument.  Useful for {@code toString()} implementations and log messages.
+     *
+     * @param array the array whose contents will be converted to a string
+     * @param delimiter the delimiter to use between each element
+     * @return a single string, delimited by the specified {@code delimiter}.
+     * @since 1.0
+     */
+    public static String toDelimitedString(Object[] array, String delimiter ) {
+        if ( array == null || array.length == 0 ) {
+            return EMPTY_STRING;
+        }
+        StringBuffer sb = new StringBuffer();
+        for( int i=0; i < array.length; i++ ) {
+            if ( i > 0 ) {
+                sb.append(delimiter);
+            }
+            sb.append(array[i]);
+        }
+        return sb.toString();
+    }
+
+    /**
      * Tokenize the given String into a String array via a StringTokenizer.
      * Trims tokens and omits empty tokens.
      * <p>The given delimiters string is supposed to consist of any number of
diff --git a/web/src/main/java/org/apache/ki/web/config/IniWebConfiguration.java b/web/src/main/java/org/apache/ki/web/config/IniWebConfiguration.java
index 3c234a1..888b599 100644
--- a/web/src/main/java/org/apache/ki/web/config/IniWebConfiguration.java
+++ b/web/src/main/java/org/apache/ki/web/config/IniWebConfiguration.java
@@ -50,6 +50,8 @@
 import org.apache.ki.web.filter.authc.UserFilter;
 import org.apache.ki.web.filter.authz.PermissionsAuthorizationFilter;
 import org.apache.ki.web.filter.authz.RolesAuthorizationFilter;
+import org.apache.ki.web.filter.authz.PortFilter;
+import org.apache.ki.web.filter.authz.SslFilter;
 import org.apache.ki.web.servlet.AdviceFilter;
 import org.apache.ki.web.servlet.ProxiedFilterChain;
 
@@ -138,7 +140,7 @@
             if (pathMatches(path, requestURI)) {
                 if (log.isTraceEnabled()) {
                     log.trace("Matched path [" + path + "] for requestURI [" + requestURI + "].  " +
-                            "Utilizing corresponding filter chain...");
+                        "Utilizing corresponding filter chain...");
                 }
                 return getChain(path, originalChain);
             }
@@ -315,8 +317,8 @@
     protected void assertFilter(String name, Object o) throws ConfigurationException {
         if (!(o instanceof Filter)) {
             String msg = "[" + FILTERS + "] section specified a filter named '" + name + "', which does not " +
-                    "implement the " + Filter.class.getName() + " interface.  Only Filter implementations may be " +
-                    "defined.";
+                "implement the " + Filter.class.getName() + " interface.  Only Filter implementations may be " +
+                "defined.";
             throw new ConfigurationException(msg);
         }
     }
@@ -354,6 +356,16 @@
         filter.setName(name);
         filters.put(name, filter);
 
+        name = "port";
+        filter = new PortFilter();
+        filter.setName(name);
+        filters.put(name, filter);
+
+        name = "ssl";
+        filter = new SslFilter();
+        filter.setName(name);
+        filters.put(name, filter);
+
         return filters;
     }
 
@@ -416,13 +428,13 @@
                 Filter filter = filters.get(name);
                 if (filter == null) {
                     String msg = "Path [" + path + "] specified a filter named '" + name + "', but that " +
-                            "filter has not been specified in the [" + FILTERS + "] section.";
+                        "filter has not been specified in the [" + FILTERS + "] section.";
                     throw new ConfigurationException(msg);
                 }
                 if (filter instanceof PathConfigProcessor) {
                     if (log.isDebugEnabled()) {
                         log.debug("Applying path [" + path + "] to filter [" + name + "] " +
-                                "with config [" + config + "]");
+                            "with config [" + config + "]");
                     }
                     ((PathConfigProcessor) filter).processPathConfig(path, config);
                 }
diff --git a/web/src/main/java/org/apache/ki/web/filter/AccessControlFilter.java b/web/src/main/java/org/apache/ki/web/filter/AccessControlFilter.java
index 13fc019..f35ef30 100644
--- a/web/src/main/java/org/apache/ki/web/filter/AccessControlFilter.java
+++ b/web/src/main/java/org/apache/ki/web/filter/AccessControlFilter.java
@@ -102,7 +102,7 @@
     /**
      * Returns <code>true</code> if the request is allowed to proceed through the filter normally, or <code>false</code>
      * if the request should be handled by the
-     * {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied(request,response)}
+     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(request,response,mappedValue)}
      * method instead.
      *
      * @param request     the incoming <code>ServletRequest</code>
@@ -110,7 +110,7 @@
      * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
      * @return <code>true</code> if the request should proceed through the filter normally, <code>false</code> if the
      *         request should be processed by this filter's
-     *         {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method instead.
+     *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
      * @throws Exception if an error occurs during processing.
      */
     protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
@@ -118,6 +118,26 @@
     /**
      * Processes requests where the subject was denied access as determined by the
      * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
+     * method, retaining the {@code mappedValue} that was used during configuration.
+     * <p/>
+     * This method immediately delegates to {@link #onAccessDenied(ServletRequest,ServletResponse)} as a
+     * convenience in that most post-denial behavior does not need the mapped config again.
+     *
+     * @param request  the incoming <code>ServletRequest</code>
+     * @param response the outgoing <code>ServletResponse</code>
+     * @param mappedValue the config specified for the filter in the matching request's filter chain.
+     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
+     *         handle/render the response directly.
+     * @throws Exception if there is an error processing the request.
+     * @since 1.0
+     */
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        return onAccessDenied(request, response);
+    }
+
+    /**
+     * Processes requests where the subject was denied access as determined by the
+     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
      * method.
      *
      * @param request  the incoming <code>ServletRequest</code>
@@ -130,9 +150,9 @@
 
     /**
      * Returns <code>true</code> if
-     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
+     * {@link #isAccessAllowed(ServletRequest,ServletResponse,Object) isAccessAllowed(Request,Response,Object)},
      * otherwise returns the result of
-     * {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied}.
+     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(Request,Response,Object)}.
      *
      * @return <code>true</code> if
      *         {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
@@ -142,7 +162,7 @@
      */
     public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
         //mapped value is ignored - not needed for most (if not all) authc Filters.
-        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response);
+        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
     }
 
     /**
diff --git a/web/src/main/java/org/apache/ki/web/filter/authz/PortFilter.java b/web/src/main/java/org/apache/ki/web/filter/authz/PortFilter.java
new file mode 100644
index 0000000..5d42921
--- /dev/null
+++ b/web/src/main/java/org/apache/ki/web/filter/authz/PortFilter.java
@@ -0,0 +1,78 @@
+package org.apache.ki.web.filter.authz;
+
+import org.apache.ki.config.ConfigurationException;
+import org.apache.ki.util.StringUtils;
+import org.apache.ki.web.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * A Filter that requires the request to be on a specific port, and if not, redirects to the same URL on that port.
+ *
+ * @author Les Hazlewood
+ * @since Mar 30, 2009 10:46:21 AM
+ */
+public class PortFilter extends AuthorizationFilter {
+
+    protected int toPort(Object mappedValue) {
+        String[] ports = (String[]) mappedValue;
+        if (ports == null || ports.length == 0) {
+            throw new ConfigurationException("PortFilter defined, but no port was specified!");
+        }
+        if (ports.length > 1) {
+            throw new ConfigurationException("PortFilter can only be configured with a single port.  You have " +
+                "configured " + ports.length + ": " + StringUtils.toString(ports));
+        }
+        return Integer.parseInt(ports[0]);
+    }
+
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        int requiredPort = toPort(mappedValue);
+        int requestPort = request.getServerPort();
+        return requiredPort == requestPort;
+    }
+
+    /**
+     * Redirects the request to the same exact incoming URL, but with the port listed in the filter's configuration.
+     *
+     * @param request     the incoming <code>ServletRequest</code>
+     * @param response    the outgoing <code>ServletResponse</code>
+     * @param mappedValue the config specified for the filter in the matching request's filter chain.
+     * @return {@code false} always to force a redirect.
+     */
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
+
+        //just redirect to the specified port:
+        int port = toPort(mappedValue);
+
+        StringBuffer sb = new StringBuffer();
+
+        String scheme = request.getScheme();
+        if (port == 80) {
+            scheme = "http";
+        } else if (port == 443) {
+            scheme = "https";
+        }
+        sb.append(scheme).append("://");
+        sb.append(request.getServerName());
+        if (port != 80 && port != 443) {
+            sb.append(":");
+            sb.append(request.getServerPort());
+        }
+        if (request instanceof HttpServletRequest) {
+            sb.append(WebUtils.toHttp(request).getRequestURI());
+            String query = WebUtils.toHttp(request).getQueryString();
+            if ( query != null ) {
+                sb.append("?").append(query);
+            }
+        }
+
+        WebUtils.issueRedirect(request, response, sb.toString());
+
+        return false;
+    }
+}
diff --git a/web/src/main/java/org/apache/ki/web/filter/authz/SslFilter.java b/web/src/main/java/org/apache/ki/web/filter/authz/SslFilter.java
new file mode 100644
index 0000000..d54cdd4
--- /dev/null
+++ b/web/src/main/java/org/apache/ki/web/filter/authz/SslFilter.java
@@ -0,0 +1,27 @@
+package org.apache.ki.web.filter.authz;
+
+/**
+ * Convenience filter which requires a request to be over SSL.  This filter has the same effect as of using the
+ * {@link PortFilter} with configuration defaulting to port {@code 443}.  That is, these two configs are the same:
+ *
+ * <pre>
+ * /some/path/** = port[443]
+ * /some/path/** = ssl
+ * </pre>
+ *
+ * @author Les Hazlewood
+ * @since Mar 30, 2009 12:16:14 PM
+ */
+public class SslFilter extends PortFilter {
+
+    public static final int DEFAULT_SSL_PORT = 443;
+
+    @Override
+    protected int toPort(Object mappedValue) {
+        String[] ports = (String[]) mappedValue;
+        if (ports == null || ports.length == 0) {
+            return DEFAULT_SSL_PORT;
+        }
+        return super.toPort(mappedValue);
+    }
+}