View Javadoc

    1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
    2    *
    3    * Licensed under the Apache License, Version 2.0 (the "License");
    4    * you may not use this file except in compliance with the License.
    5    * You may obtain a copy of the License at
    6    *
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    *
    9    * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15
    16  package org.acegisecurity.captcha;
    17
    18  import org.acegisecurity.securechannel.ChannelEntryPoint;
    19  import org.acegisecurity.util.PortMapper;
    20  import org.acegisecurity.util.PortMapperImpl;
    21  import org.acegisecurity.util.PortResolver;
    22  import org.acegisecurity.util.PortResolverImpl;
    23
    24  import org.apache.commons.logging.Log;
    25  import org.apache.commons.logging.LogFactory;
    26
    27  import org.springframework.beans.factory.InitializingBean;
    28
    29  import org.springframework.util.Assert;
    30
    31  import java.io.IOException;
    32  import java.io.UnsupportedEncodingException;
    33
    34  import java.net.URLEncoder;
    35
    36  import java.util.Enumeration;
    37
    38  import javax.servlet.ServletException;
    39  import javax.servlet.ServletRequest;
    40  import javax.servlet.ServletResponse;
    41  import javax.servlet.http.HttpServletRequest;
    42  import javax.servlet.http.HttpServletResponse;
    43
    44
    45  /***
    46   * The captcha entry point : redirect to the captcha test page. <br>
    47   *
    48   * <p>
    49   * This entry point can force the use of SSL : see {@link #getForceHttps()}<br>
    50   * </p>
    51   * This entry point allows internal OR external redirect : see {@link #setOutsideWebApp(boolean)}<br>
    52   * / Original request can be added to the redirect path using a custom
    53   * translation : see {@link #setIncludeOriginalRequest(boolean)}<br>
    54   * Original request is translated using URLEncoding and the following
    55   * translation mapping in the redirect url :
    56   *
    57   * <ul>
    58   * <li>
    59   * original url => {@link #getOriginalRequestUrlParameterName()}
    60   * </li>
    61   * <li>
    62   * If {@link #isIncludeOriginalParameters()}
    63   * </li>
    64   * <li>
    65   * original method => {@link #getOriginalRequestMethodParameterName()}
    66   * </li>
    67   * <li>
    68   * original parameters => {@link #getOriginalRequestParametersParameterName()}
    69   * </li>
    70   * <li>
    71   * The original parameters string is contructed using :
    72   *
    73   * <ul>
    74   * <li>
    75   * a parameter separator {@link #getOriginalRequestParametersSeparator()}
    76   * </li>
    77   * <li>
    78   * a parameter name value pair separator for each parameter {@link
    79   * #getOriginalRequestParametersNameValueSeparator()}
    80   * </li>
    81   * </ul>
    82   *
    83   * </li>
    84   * </ul>
    85   *
    86   * <br><br>
    87   * Default values :<br>
    88   * forceHttps = false<br>
    89   * includesOriginalRequest = true<br>
    90   * includesOriginalParameters = false<br>
    91   * isOutsideWebApp=false<br>
    92   * originalRequestUrlParameterName  =original_requestUrl <br>
    93   * originalRequestParametersParameterName = original_request_parameters<br>
    94   * originalRequestParametersNameValueSeparator =   __ <br>
    95   * originalRequestParametersSeparator =  ;; <br>
    96   * originalRequestMethodParameterName =  original_request_method    <br>
    97   * urlEncodingCharset = UTF-8<br>
    98   *
    99   * @author marc antoine Garrigue
    100  * @version $Id: CaptchaEntryPoint.java,v 1.4 2005/11/17 00:55:49 benalex Exp $
    101  */
    102 public class CaptchaEntryPoint implements ChannelEntryPoint, InitializingBean {
    103     //~ Static fields/initializers =============================================
    104
    105     // ~ Static fields/initializers
    106     // =============================================
    107     private static final Log logger = LogFactory.getLog(CaptchaEntryPoint.class);
    108
    109     //~ Instance fields ========================================================
    110
    111     // ~ Instance fields
    112     // ========================================================
    113     private PortMapper portMapper = new PortMapperImpl();
    114     private PortResolver portResolver = new PortResolverImpl();
    115     private String captchaFormUrl;
    116     private String originalRequestMethodParameterName = "original_request_method";
    117     private String originalRequestParametersNameValueSeparator = "__";
    118     private String originalRequestParametersParameterName = "original_request_parameters";
    119     private String originalRequestParametersSeparator = ";;";
    120     private String originalRequestUrlParameterName = "original_requestUrl";
    121     private String urlEncodingCharset = "UTF-8";
    122     private boolean forceHttps = false;
    123     private boolean includeOriginalParameters = false;
    124     private boolean includeOriginalRequest = true;
    125     private boolean isOutsideWebApp = false;
    126
    127     //~ Methods ================================================================
    128
    129     /***
    130      * The URL where the <code>CaptchaProcessingFilter</code> login page can be
    131      * found. Should be relative to the web-app context path, and include a
    132      * leading <code>/</code>
    133      *
    134      * @param captchaFormUrl
    135      */
    136     public void setCaptchaFormUrl(String captchaFormUrl) {
    137         this.captchaFormUrl = captchaFormUrl;
    138     }
    139
    140     /***
    141      * DOCUMENT ME!
    142      *
    143      * @return the captcha test page to redirect to.
    144      */
    145     public String getCaptchaFormUrl() {
    146         return captchaFormUrl;
    147     }
    148
    149     // ~ Methods
    150     // ================================================================
    151
    152     /***
    153      * Set to true to force captcha form access to be via https. If this value
    154      * is ture (the default is false), and the incoming request for the
    155      * protected resource which triggered the interceptor was not already
    156      * <code>https</code>, then
    157      *
    158      * @param forceHttps
    159      */
    160     public void setForceHttps(boolean forceHttps) {
    161         this.forceHttps = forceHttps;
    162     }
    163
    164     public boolean getForceHttps() {
    165         return forceHttps;
    166     }
    167
    168     public void setIncludeOriginalParameters(boolean includeOriginalParameters) {
    169         this.includeOriginalParameters = includeOriginalParameters;
    170     }
    171
    172     public boolean isIncludeOriginalParameters() {
    173         return includeOriginalParameters;
    174     }
    175
    176     /***
    177      * If set to true, the original request url will be appended to the
    178      * redirect url using the {@link #getOriginalRequestUrlParameterName()}.
    179      *
    180      * @param includeOriginalRequest
    181      */
    182     public void setIncludeOriginalRequest(boolean includeOriginalRequest) {
    183         this.includeOriginalRequest = includeOriginalRequest;
    184     }
    185
    186     public boolean isIncludeOriginalRequest() {
    187         return includeOriginalRequest;
    188     }
    189
    190     public void setOriginalRequestMethodParameterName(
    191         String originalRequestMethodParameterName) {
    192         this.originalRequestMethodParameterName = originalRequestMethodParameterName;
    193     }
    194
    195     public String getOriginalRequestMethodParameterName() {
    196         return originalRequestMethodParameterName;
    197     }
    198
    199     public void setOriginalRequestParametersNameValueSeparator(
    200         String originalRequestParametersNameValueSeparator) {
    201         this.originalRequestParametersNameValueSeparator = originalRequestParametersNameValueSeparator;
    202     }
    203
    204     public String getOriginalRequestParametersNameValueSeparator() {
    205         return originalRequestParametersNameValueSeparator;
    206     }
    207
    208     public void setOriginalRequestParametersParameterName(
    209         String originalRequestParametersParameterName) {
    210         this.originalRequestParametersParameterName = originalRequestParametersParameterName;
    211     }
    212
    213     public String getOriginalRequestParametersParameterName() {
    214         return originalRequestParametersParameterName;
    215     }
    216
    217     public void setOriginalRequestParametersSeparator(
    218         String originalRequestParametersSeparator) {
    219         this.originalRequestParametersSeparator = originalRequestParametersSeparator;
    220     }
    221
    222     public String getOriginalRequestParametersSeparator() {
    223         return originalRequestParametersSeparator;
    224     }
    225
    226     public void setOriginalRequestUrlParameterName(
    227         String originalRequestUrlParameterName) {
    228         this.originalRequestUrlParameterName = originalRequestUrlParameterName;
    229     }
    230
    231     public String getOriginalRequestUrlParameterName() {
    232         return originalRequestUrlParameterName;
    233     }
    234
    235     /***
    236      * if set to true, the {@link #commence(ServletRequest, ServletResponse)}
    237      * method uses the {@link #getCaptchaFormUrl()} as a complete URL, else it
    238      * as a 'inside WebApp' path.
    239      *
    240      * @param isOutsideWebApp
    241      */
    242     public void setOutsideWebApp(boolean isOutsideWebApp) {
    243         this.isOutsideWebApp = isOutsideWebApp;
    244     }
    245
    246     public boolean isOutsideWebApp() {
    247         return isOutsideWebApp;
    248     }
    249
    250     public void setPortMapper(PortMapper portMapper) {
    251         this.portMapper = portMapper;
    252     }
    253
    254     public PortMapper getPortMapper() {
    255         return portMapper;
    256     }
    257
    258     public void setPortResolver(PortResolver portResolver) {
    259         this.portResolver = portResolver;
    260     }
    261
    262     public PortResolver getPortResolver() {
    263         return portResolver;
    264     }
    265
    266     public void setUrlEncodingCharset(String urlEncodingCharset) {
    267         this.urlEncodingCharset = urlEncodingCharset;
    268     }
    269
    270     public String getUrlEncodingCharset() {
    271         return urlEncodingCharset;
    272     }
    273
    274     public void afterPropertiesSet() throws Exception {
    275         Assert.hasLength(captchaFormUrl, "captchaFormUrl must be specified");
    276         Assert.hasLength(originalRequestMethodParameterName,
    277             "originalRequestMethodParameterName must be specified");
    278         Assert.hasLength(originalRequestParametersNameValueSeparator,
    279             "originalRequestParametersNameValueSeparator must be specified");
    280         Assert.hasLength(originalRequestParametersParameterName,
    281             "originalRequestParametersParameterName must be specified");
    282         Assert.hasLength(originalRequestParametersSeparator,
    283             "originalRequestParametersSeparator must be specified");
    284         Assert.hasLength(originalRequestUrlParameterName,
    285             "originalRequestUrlParameterName must be specified");
    286         Assert.hasLength(urlEncodingCharset,
    287             "urlEncodingCharset must be specified");
    288         Assert.notNull(portMapper, "portMapper must be specified");
    289         Assert.notNull(portResolver, "portResolver must be specified");
    290         URLEncoder.encode("   fzaef é& à ", urlEncodingCharset);
    291     }
    292
    293     public void commence(ServletRequest request, ServletResponse response)
    294         throws IOException, ServletException {
    295         StringBuffer redirectUrl = new StringBuffer();
    296         HttpServletRequest req = (HttpServletRequest) request;
    297
    298         if (isOutsideWebApp) {
    299             redirectUrl = redirectUrl.append(captchaFormUrl);
    300         } else {
    301             buildInternalRedirect(redirectUrl, req);
    302         }
    303
    304         if (includeOriginalRequest) {
    305             includeOriginalRequest(redirectUrl, req);
    306         }
    307
    308         // add post parameter? DONE!
    309         if (logger.isDebugEnabled()) {
    310             logger.debug("Redirecting to: " + redirectUrl);
    311         }
    312
    313         ((HttpServletResponse) response).sendRedirect(redirectUrl.toString());
    314     }
    315
    316     private void buildInternalRedirect(StringBuffer redirectUrl,
    317         HttpServletRequest req) {
    318         // construct it
    319         StringBuffer simpleRedirect = new StringBuffer();
    320
    321         String scheme = req.getScheme();
    322         String serverName = req.getServerName();
    323         int serverPort = portResolver.getServerPort(req);
    324         String contextPath = req.getContextPath();
    325         boolean includePort = true;
    326
    327         if ("http".equals(scheme.toLowerCase()) && (serverPort == 80)) {
    328             includePort = false;
    329         }
    330
    331         if ("https".equals(scheme.toLowerCase()) && (serverPort == 443)) {
    332             includePort = false;
    333         }
    334
    335         simpleRedirect.append(scheme);
    336         simpleRedirect.append("://");
    337         simpleRedirect.append(serverName);
    338
    339         if (includePort) {
    340             simpleRedirect.append(":");
    341             simpleRedirect.append(serverPort);
    342         }
    343
    344         simpleRedirect.append(contextPath);
    345         simpleRedirect.append(captchaFormUrl);
    346
    347         if (forceHttps && req.getScheme().equals("http")) {
    348             Integer httpPort = new Integer(portResolver.getServerPort(req));
    349             Integer httpsPort = (Integer) portMapper.lookupHttpsPort(httpPort);
    350
    351             if (httpsPort != null) {
    352                 if (httpsPort.intValue() == 443) {
    353                     includePort = false;
    354                 } else {
    355                     includePort = true;
    356                 }
    357
    358                 redirectUrl.append("https://");
    359                 redirectUrl.append(serverName);
    360
    361                 if (includePort) {
    362                     redirectUrl.append(":");
    363                     redirectUrl.append(httpsPort);
    364                 }
    365
    366                 redirectUrl.append(contextPath);
    367                 redirectUrl.append(captchaFormUrl);
    368             } else {
    369                 redirectUrl.append(simpleRedirect);
    370             }
    371         } else {
    372             redirectUrl.append(simpleRedirect);
    373         }
    374     }
    375
    376     private void includeOriginalRequest(StringBuffer redirectUrl,
    377         HttpServletRequest req) {
    378         // add original request to the url
    379         if (redirectUrl.indexOf("?") >= 0) {
    380             redirectUrl.append("&");
    381         } else {
    382             redirectUrl.append("?");
    383         }
    384
    385         redirectUrl.append(originalRequestUrlParameterName);
    386         redirectUrl.append("=");
    387
    388         try {
    389             redirectUrl.append(URLEncoder.encode(req.getRequestURL().toString(),
    390                     urlEncodingCharset));
    391         } catch (UnsupportedEncodingException e) {
    392             logger.warn(e);
    393         }
    394
    395         //append method
    396         redirectUrl.append("&");
    397         redirectUrl.append(originalRequestMethodParameterName);
    398         redirectUrl.append("=");
    399         redirectUrl.append(req.getMethod());
    400
    401         if (includeOriginalParameters) {
    402             // append query params
    403             redirectUrl.append("&");
    404             redirectUrl.append(originalRequestParametersParameterName);
    405             redirectUrl.append("=");
    406
    407             StringBuffer qp = new StringBuffer();
    408             Enumeration parameters = req.getParameterNames();
    409
    410             if ((parameters != null) && parameters.hasMoreElements()) {
    411                 //qp.append("?");
    412                 while (parameters.hasMoreElements()) {
    413                     String name = parameters.nextElement().toString();
    414                     String value = req.getParameter(name);
    415                     qp.append(name);
    416                     qp.append(originalRequestParametersNameValueSeparator);
    417                     qp.append(value);
    418
    419                     if (parameters.hasMoreElements()) {
    420                         qp.append(originalRequestParametersSeparator);
    421                     }
    422                 }
    423             }
    424
    425             try {
    426                 redirectUrl.append(URLEncoder.encode(qp.toString(),
    427                         urlEncodingCharset));
    428             } catch (Exception e) {
    429                 logger.warn(e);
    430             }
    431         }
    432     }
    433 }