2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

View Javadoc

1   /* 
2    * ========================================================================
3    * 
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   * 
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * 
19   * ========================================================================
20   */
21  package org.apache.cactus.client.authentication;
22  
23  import java.net.HttpURLConnection;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  
27  import org.apache.cactus.Cookie;
28  import org.apache.cactus.WebRequest;
29  import org.apache.cactus.internal.HttpServiceDefinition;
30  import org.apache.cactus.internal.ServiceEnumeration;
31  import org.apache.cactus.internal.WebRequestImpl;
32  import org.apache.cactus.internal.client.connector.http.HttpClientConnectionHelper;
33  import org.apache.cactus.internal.configuration.Configuration;
34  import org.apache.cactus.internal.configuration.WebConfiguration;
35  import org.apache.cactus.util.ChainedRuntimeException;
36  import org.apache.commons.httpclient.HttpMethod;
37  import org.apache.commons.httpclient.HttpState;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  /**
42   * Form-based authentication implementation. An instance of this class
43   * can be reused across several tests as it caches the session cookie.
44   * Thus the first time it is used to authenticate the user, it calls
45   * the security URL (which is by default the context URL prepended by
46   * "j_security_check"), caches the returned session cookie and adds the
47   * cookie for the next request. The second time it is called, it simply
48   * addes the session cookie for the next request.
49   * 
50   * @since 1.5
51   *
52   * @version $Id: FormAuthentication.java 238991 2004-05-22 11:34:50Z vmassol $
53   */
54  public class FormAuthentication extends AbstractAuthentication
55  {
56      /**
57       * The logger.
58       */
59      private static final Log LOGGER = 
60          LogFactory.getLog(FormAuthentication.class);
61  
62      /**
63       * The expected HTTP response status code when the authentication
64       * is succeeded.
65       */
66      private int expectedAuthResponse = HttpURLConnection.HTTP_MOVED_TEMP;
67  
68      /**
69       * The URL to use when attempting to log in, if for whatever reason 
70       * the default URL is incorrect.
71       */
72      private URL securityCheckURL;
73  
74      /**
75       * The cookie name of the session.
76       */
77      private String sessionCookieName = "JSESSIONID";
78  
79      /**
80       * We store the session cookie.
81       */
82      private Cookie jsessionCookie;
83  
84      /**
85       * {@link WebRequest} object that will be used to connect to the
86       * security URL. 
87       */
88      private WebRequest securityRequest = new WebRequestImpl();
89        
90      /**
91       * @param theName user name of the Credential
92       * @param thePassword user password of the Credential
93       */
94      public FormAuthentication(String theName, String thePassword)
95      {
96          super(theName, thePassword);
97      }
98      
99      /**
100      * {@inheritDoc}
101      * @see Authentication#configure
102      */
103     public void configure(HttpState theState, HttpMethod theMethod,
104         WebRequest theRequest, Configuration theConfiguration)
105     {
106         // Only authenticate the first time this instance is used.
107         if (this.jsessionCookie == null)
108         {
109            authenticate(theRequest, theConfiguration);
110         }
111 
112         // Sets the session id cookie for the next request.
113         if (this.jsessionCookie != null)
114         {
115             theRequest.addCookie(this.jsessionCookie);
116         }
117     }
118 
119     /**
120      * @return the {@link WebRequest} that will be used to connect to the
121      * security URL. It can be used to add additional HTTP parameters such
122      * as proprietary ones required by some containers.
123      */
124     public WebRequest getSecurityRequest()
125     {
126         return this.securityRequest;
127     }
128     
129     /**
130      * This sets the URL to use when attempting to log in. This method is used
131      * if for whatever reason the default URL is incorrect.
132      *
133      * @param theUrl A URL to use to attempt to login.
134      */
135     public void setSecurityCheckURL(URL theUrl)
136     {
137         this.securityCheckURL = theUrl;
138     }
139     
140     /**
141      * This returns the URL to use when attempting to log in. By default, it's
142      * the context URL defined in the Cactus configuration with  
143      * "/j_security_check" appended. 
144      *
145      * @param theConfiguration the Cactus configuration
146      * @return the URL that is being used to attempt to login.
147      */
148     public URL getSecurityCheckURL(Configuration theConfiguration)
149     {
150         if (this.securityCheckURL == null)
151         {
152             // Configure default
153             String stringUrl = 
154                 ((WebConfiguration) theConfiguration).getContextURL()
155                 + "/j_security_check";
156 
157             try
158             {
159                 this.securityCheckURL = new URL(stringUrl);
160             }
161             catch (MalformedURLException e)
162             {
163                 throw new ChainedRuntimeException(
164                     "Unable to create default Security Check URL [" 
165                     + stringUrl + "]");
166             }
167         }
168 
169         LOGGER.debug("Using security check URL [" + this.securityCheckURL
170             + "]");
171 
172         return securityCheckURL;
173     }
174 
175 
176     /**
177      * Get the cookie name of the session.
178      * @return the cookie name of the session
179      */
180     private String getSessionCookieName()
181     {
182         return this.sessionCookieName;
183     }
184 
185     /**
186      * Set the cookie name of the session to theName.
187      * If theName is null, the change request will be ignored.
188      * The default is &quot;<code>JSESSIONID</code>&quot;.
189      * @param theName the cookie name of the session
190      */
191     public void setSessionCookieName(String theName)
192     {
193         if (theName != null)
194         {
195             this.sessionCookieName = theName;
196         }
197     }
198 
199 
200     /**
201      * Get the expected HTTP response status code for an authentication request
202      * which should be successful.
203      * @return the expected HTTP response status code
204      */
205     protected int getExpectedAuthResponse()
206     {
207         return this.expectedAuthResponse;
208     }
209 
210     /**
211      * Set the expected HTTP response status code for an authentication request
212      * which should be successful.
213      * The default is HttpURLConnection.HTTP_MOVED_TEMP.
214      * @param theExpectedCode the expected HTTP response status code value
215      */
216     public void setExpectedAuthResponse(int theExpectedCode)
217     {
218         this.expectedAuthResponse = theExpectedCode;
219     }
220 
221 
222     /**
223      * Get a cookie required to be set by set-cookie header field.
224      * @param theConnection a {@link HttpURLConnection}
225      * @param theTarget the target cookie name
226      * @return the {@link Cookie}
227      */
228     private Cookie getCookie(HttpURLConnection theConnection, String theTarget)
229     {
230         // Check (possible multiple) cookies for a target.
231         int i = 1;
232         String key = theConnection.getHeaderFieldKey(i);
233         while (key != null)
234         {
235             if (key.equalsIgnoreCase("set-cookie"))
236             {
237                 // Cookie is in the form:
238                 // "NAME=VALUE; expires=DATE; path=PATH;
239                 //  domain=DOMAIN_NAME; secure"
240                 // The only thing we care about is finding a cookie with
241                 // the name "JSESSIONID" and caching the value.
242                 String cookiestr = theConnection.getHeaderField(i);
243                 String nameValue = cookiestr.substring(0, 
244                     cookiestr.indexOf(";"));
245                 int equalsChar = nameValue.indexOf("=");
246                 String name = nameValue.substring(0, equalsChar);
247                 String value = nameValue.substring(equalsChar + 1);
248                 if (name.equalsIgnoreCase(theTarget))
249                 {
250                     return new Cookie(theConnection.getURL().getHost(),
251                         name, value);
252                 }
253             }
254             key = theConnection.getHeaderFieldKey(++i);
255         }
256         return null;
257     }
258 
259 
260     /**
261      * Check if the pre-auth step can be considered as succeeded or not.
262      * As default, the step considered as succeeded
263      * if the response status code of <code>theConnection</code>
264      * is less than 400.
265      *
266      * @param theConnection a <code>HttpURLConnection</code> value
267      * @exception Exception if the pre-auth step should be considered as failed
268      */
269     protected void checkPreAuthResponse(HttpURLConnection theConnection)
270         throws Exception
271     {
272         if (theConnection.getResponseCode() >= 400)
273         {
274             throw new Exception("Received a status code ["
275                 + theConnection.getResponseCode()
276                 + "] and was expecting less than 400");
277         }
278     }
279     
280     /**
281      * @param theRequest a <code>WebRequest</code> value
282      * @param theConfiguration a <code>Configuration</code> value
283      * @exception Exception if the post-auth request results response
284      *    other than 200 (OK).
285      */
286     protected void checkPostAuthRequest(WebRequest theRequest,
287                                         Configuration theConfiguration)
288         throws Exception
289     {
290         HttpURLConnection connection;
291         String resource = null;
292 
293         try
294         {
295             // Create a helper that will connect to a restricted resource.
296             WebConfiguration webConfig = (WebConfiguration) theConfiguration;
297             resource = webConfig.getRedirectorURL(theRequest);
298 
299             HttpClientConnectionHelper helper =
300                 new HttpClientConnectionHelper(resource);
301 
302             WebRequest request = getDummyRequest(webConfig);
303             request.addCookie(this.jsessionCookie);
304 
305             // Make the connection using a default web request.
306             connection = helper.connect(request, theConfiguration);
307         }
308         catch (Throwable e)
309         {
310             throw new ChainedRuntimeException(
311                 "Failed to connect to the secured redirector: " + resource, e);
312         }
313 
314         if (connection.getResponseCode() != 200)
315         {
316             throw new Exception("Received a status code ["
317                 + connection.getResponseCode()
318                 + "] and was expecting 200");
319 
320         }
321     }
322 
323     /**
324      * @param theWebConfiguration the <code>WebConfiguration</code> value
325      * @return WebReuest instance
326      */
327     private WebRequest getDummyRequest(WebConfiguration theWebConfiguration)
328     {
329         WebRequest request = new WebRequestImpl(theWebConfiguration);
330         request.addParameter(HttpServiceDefinition.SERVICE_NAME_PARAM,
331             ServiceEnumeration.RUN_TEST_SERVICE.toString());
332         return request;
333     }
334 
335 
336     
337 
338 
339     /**
340      * Get login session cookie.
341      * This is the first step to start login session:
342      * <ol>
343      *   <dt> C-&gt;S: </dt>
344      *   <dd> try to connect to a restricted resource </dd>
345      *   <dt> S-&gt;C: </dt>
346      *   <dd> redirect or forward to the login page with set-cookie header </dd>
347      * </ol>
348      * @param theRequest a request to connect to a restricted resource
349      * @param theConfiguration a <code>Configuration</code> value
350      * @return the <code>Cookie</code>
351      */
352     private Cookie getSecureSessionIdCookie(WebRequest theRequest,
353         Configuration theConfiguration)
354     {
355         HttpURLConnection connection;
356         String resource = null;
357 
358         try
359         {
360             // Create a helper that will connect to a restricted resource.
361             WebConfiguration webConfig = (WebConfiguration) theConfiguration;
362             resource = webConfig.getRedirectorURL(theRequest);
363 
364             HttpClientConnectionHelper helper = 
365                 new HttpClientConnectionHelper(resource);
366 
367             WebRequest request = getDummyRequest(webConfig);
368 
369             // Make the connection using a default web request.
370             connection = helper.connect(request, theConfiguration);
371 
372             checkPreAuthResponse(connection);
373         }
374         catch (Throwable e)
375         {
376             throw new ChainedRuntimeException(
377                 "Failed to connect to the secured redirector: " + resource, e);
378         }
379 
380         return getCookie(connection, getSessionCookieName());
381     }
382 
383 
384     /**
385      * Check if the auth step can be considered as succeeded or not.
386      * As default, the step considered as succeeded
387      * if the response status code of <code>theConnection</code>
388      * equals <code>getExpectedAuthResponse()</code>.
389      *
390      * @param theConnection a <code>HttpURLConnection</code> value
391      * @exception Exception if the auth step should be considered as failed
392      */
393     protected void checkAuthResponse(HttpURLConnection theConnection)
394         throws Exception
395     {
396         if (theConnection.getResponseCode() != getExpectedAuthResponse())
397         {
398             throw new Exception("Received a status code ["
399                 + theConnection.getResponseCode()
400                 + "] and was expecting a ["
401                 + getExpectedAuthResponse() + "]");
402         }
403     }
404 
405 
406     /**
407      * Authenticate the principal by calling the security URL.
408      * 
409      * @param theRequest the web request used to connect to the Redirector
410      * @param theConfiguration the Cactus configuration
411      */
412     public void authenticate(WebRequest theRequest,
413         Configuration theConfiguration)
414     {
415         this.jsessionCookie = getSecureSessionIdCookie(theRequest,
416             theConfiguration);
417     
418         try
419         {
420             // Create a helper that will connect to the security check URL.
421             HttpClientConnectionHelper helper = 
422                 new HttpClientConnectionHelper(
423                     getSecurityCheckURL(theConfiguration).toString());
424 
425             // Configure a web request with the JSESSIONID cookie,
426             // the username and the password.
427             WebRequest request = getSecurityRequest();
428             ((WebRequestImpl) request).setConfiguration(theConfiguration);
429             request.addCookie(this.jsessionCookie);
430             request.addParameter("j_username", getName(), 
431                 WebRequest.POST_METHOD);
432             request.addParameter("j_password", getPassword(), 
433                 WebRequest.POST_METHOD);
434 
435             // Make the connection using the configured web request.
436             HttpURLConnection connection = helper.connect(request,
437                 theConfiguration);
438 
439             checkAuthResponse(connection);        
440             checkPostAuthRequest(theRequest, theConfiguration);
441         }
442         catch (Throwable e)
443         {
444             this.jsessionCookie = null;
445             throw new ChainedRuntimeException(
446                 "Failed to authenticate the principal", e);
447         }
448     }
449 }