View Javadoc

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