View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 William Chesters
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     William Chesters <williamc At paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati.login;
47  
48  
49  import javax.servlet.http.Cookie;
50  import javax.servlet.http.HttpServletRequest;
51  import javax.servlet.http.HttpServletResponse;
52  import javax.servlet.http.HttpSession;
53  
54  import org.melati.Melati;
55  import org.melati.poem.AccessPoemException;
56  import org.melati.poem.PoemThread;
57  import org.melati.poem.User;
58  import org.melati.util.HttpServletRequestParameters;
59  import org.melati.util.HttpUtil;
60  import org.melati.util.MD5Util;
61  import org.melati.util.ReconstructedHttpServletRequest;
62  import org.melati.util.ReconstructedHttpServletRequestMismatchException;
63  import org.melati.util.UTF8URLEncoder;
64  
65  /**
66   * An {@link AccessHandler} which uses <code>Session</code> cookies to
67   * elicit and maintain the user's login and password.  
68   */
69  public class HttpSessionAccessHandler implements AccessHandler {
70  
71    /** Class name. */
72    public static final String
73       OVERLAY_PARAMETERS = 
74         "org.melati.login.HttpSessionAccessHandler.overlayParameters";
75    /** Class name. */
76    public static final String
77       USER = 
78         "org.melati.login.HttpSessionAccessHandler.user";
79  
80    /**
81     * The class name of the class implementing the login servlet.  Unless
82     * overridden, this is <TT>org.melati.login.Login</TT>.
83     *
84     * @return the class name of login servlet
85     * @see org.melati.login.Login
86     */
87    protected String loginPageServletClassName() {
88      return "org.melati.login.Login";
89    }
90  
91    /**
92     * The URL of the login servlet.  Unless overridden, this is computed by
93     * substituting {@link #loginPageServletClassName()} into the URL of the
94     * request being serviced.
95     *
96     * @param melati   the current Melati
97     * @param request  the request currently being serviced
98     *
99     * @return the login page url
100    * @see #loginPageServletClassName
101    */
102   public String loginPageURL(Melati melati, HttpServletRequest request) {
103     StringBuffer url = new StringBuffer();
104     HttpUtil.appendRelativeZoneURL(url, request);
105     url.append('/');
106     url.append(loginPageServletClassName());
107     url.append('/');
108     url.append(melati.getPoemContext().getLogicalDatabase());
109     url.append('/');
110 
111     return url.toString();
112   }
113 
114 
115   /**
116    * Store the current request and redirect to the login page. 
117    * 
118    * {@inheritDoc}
119    * @see org.melati.login.AccessHandler#handleAccessException
120    */
121   public void handleAccessException(Melati melati, 
122                                     AccessPoemException accessException) 
123       throws Exception {
124     HttpServletRequest request = melati.getRequest();
125     HttpServletResponse response = melati.getResponse();
126     HttpSession session = request.getSession(true);
127     session.setAttribute(Login.TRIGGERING_REQUEST_PARAMETERS,
128                      new HttpServletRequestParameters(request));
129     session.setAttribute(Login.TRIGGERING_EXCEPTION, accessException);
130     melati.getWriter().reset();
131     response.sendRedirect(loginPageURL(melati, request));
132   }
133 
134   /**
135    * Set the Access token to be used for this request.  
136    *
137    * The Access Token is either picked up from the session, or from a cookie. 
138    * The cookie is keyed on the logical database and is used to 
139    * retrieve the user's login. 
140    * The login is used (with the logical database name) to retrieve an encoded
141    * password which is then checked.
142    *
143    * {@inheritDoc}
144    * @see org.melati.login.AccessHandler#establishUser(org.melati.Melati)
145    */
146   public Melati establishUser(Melati melati) {
147     // now when we establish a user, we must also set the cookie
148     String ldb = melati.getPoemContext().getLogicalDatabase();
149     HttpSession session = melati.getSession();
150     synchronized (session) {
151       User user = (User)session.getAttribute(USER);
152       if (user == null) {
153         user = getUserFromCookie(melati,ldb);
154         if (user != null) {
155           String cookie = getCookieValue(melati,ldb+user.getLogin());
156           if (cookie == null || 
157               !cookie.equals(MD5Util.encode(user.getPassword())))
158             user = null;
159         }
160       }
161       logUsIn(melati,user);
162     }
163     return melati;
164   }
165   
166   /**
167    * Set our AccessToken.
168    * NOTE Remember a User isa Token.
169    * 
170    * @param melati the Melati to get our database from
171    * @param user the token to set
172    */
173   protected void logUsIn(Melati melati, User user) {
174     PoemThread.setAccessToken(
175         user == null ? melati.getDatabase().guestAccessToken() : user);
176   }
177 
178   
179   /**
180    * Extract User via the cookie.
181    * @param melati our Melati
182    * @param key cookie key
183    * @return the found User or null
184    */
185   User getUserFromCookie(Melati melati,String key) {
186     String login = getCookieValue(melati,key);
187     if (login == null) return null;
188     return (User)melati.getDatabase().getUserTable().getLoginColumn().
189               firstWhereEq(login);
190   }
191 
192   /**
193    * Extract a value from the cookies.
194    * 
195    * @param melati the Melati in which the Request and its cookies are stored
196    * @param key the key we need the value of
197    * @return the cookie value or null
198    */
199   String getCookieValue(Melati melati,String key) {
200     // try and get from cookie
201     // Use default encoding, regardless of user's encoding
202     key = UTF8URLEncoder.encode(key);
203     Cookie[] cookies = melati.getRequest().getCookies();
204     if(cookies == null) return null;
205     for (int i=0; i<cookies.length; i++) {
206       Cookie c = cookies[i];
207         if (c.getName().equals(key)) 
208           return UTF8URLEncoder.decode(c.getValue());
209     }
210     return null;
211   }
212 
213   /**
214    * If we are returning from a login rebuild the original request,
215    * otherwise do nothing.
216    *  
217    * {@inheritDoc}
218    * @see org.melati.login.AccessHandler#buildRequest(org.melati.Melati)
219    */
220   public void buildRequest(Melati melati) 
221       throws ReconstructedHttpServletRequestMismatchException {
222     HttpSession session = melati.getSession();
223 
224     // First off, is the user continuing after a login?  If so, we want to
225     // recover any POSTed fields from the request that triggered it.
226 
227     synchronized (session) {
228       HttpServletRequestParameters oldParams =
229           (HttpServletRequestParameters)session.getAttribute(OVERLAY_PARAMETERS);
230 
231       if (oldParams != null) {
232         session.removeAttribute(OVERLAY_PARAMETERS);
233 
234         // we don't want to create a new object here, rather we are simply 
235         // going to set up the old request parameters
236 
237         melati.setRequest(
238             new ReconstructedHttpServletRequest(oldParams, 
239                                                 melati.getRequest()));
240       }
241     }
242   }
243 }