View Javadoc

1   /*
2    * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/login/HttpBasicAuthenticationAccessHandler.java,v $
3    * $Revision: 1.38 $
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  import java.io.IOException;
49  import javax.servlet.http.HttpServletResponse;
50  import org.melati.poem.AccessPoemException;
51  import org.melati.poem.PoemThread;
52  import org.melati.poem.User;
53  import org.melati.Melati;
54  import org.melati.poem.util.StringUtils;
55  import org.melati.util.UnexpectedExceptionException;
56  
57  
58  
59  /**
60   * An {@link AccessHandler} which uses the HTTP Basic Authentication scheme to
61   * elicit and maintain the user's login and password.
62   *
63   * This implementation doesn't use the servlet session at all,
64   * so it doesn't try to send cookies or
65   * do URL rewriting.
66   *
67   */
68  public class HttpBasicAuthenticationAccessHandler implements AccessHandler {
69    private static final String className =
70            new HttpBasicAuthenticationAccessHandler().getClass().getName();
71  
72    final String REALM = className + ".realm";
73    final String USER = className + ".user";
74  
75    /**
76     * Change here to use session, if that makes sense.
77     * @return false
78     */
79    protected boolean useSession() {
80      return false;
81    }
82  
83    /**
84     * Force a login by sending a 401 error back to the browser.
85     * 
86     * HACK Apache/Netscape appear not to do anything with message, which is
87     * why it's just left as a String.
88     */
89    protected void forceLogin(HttpServletResponse resp,
90                              String realm, String message) {
91      String desc = realm == null ? "<unknown>"
92                                  : StringUtils.tr(realm, '"', ' ');
93      resp.setHeader("WWW-Authenticate", "Basic realm=\"" + desc + "\"");
94      // I don't believe there is a lot we can do about an IO exception here
95      try {
96        resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, message);
97      } catch (IOException e) {
98        throw new UnexpectedExceptionException(e);
99      }
100   }
101 
102  /**
103   * Called when an AccessPoemException is trapped.
104   *
105   * @param melati the Melati
106   * @param accessException the particular access exception to handle
107    * {@inheritDoc}
108    * @see org.melati.login.AccessHandler#
109    *   handleAccessException(org.melati.Melati, 
110    *                         org.melati.poem.AccessPoemException)
111    */
112   public void handleAccessException(Melati melati,
113                                     AccessPoemException accessException)
114       throws Exception {
115     String capName = "melati";
116     if (useSession())
117       melati.getSession().setAttribute(REALM, capName);
118     forceLogin(melati.getResponse(), capName, accessException.getMessage());
119   }
120 
121   /**
122    * Get the users details.
123    *
124    * {@inheritDoc}
125    * @see org.melati.login.AccessHandler#establishUser(org.melati.Melati)
126    */
127   public Melati establishUser(Melati melati) {
128 
129     HttpAuthorization auth = HttpAuthorization.from(melati.getRequest());
130 
131     if (auth == null) {
132       // No attempt to log in: become `guest'
133 
134       PoemThread.setAccessToken(melati.getDatabase().guestAccessToken());
135       return melati;
136     }
137     else {
138       // They are trying to log in
139 
140       // If allowed, we store the User in the session to avoid repeating the
141       // SELECTion implied by firstWhereEq for every hit
142 
143       User sessionUser =
144           useSession() ? (User)melati.getSession().getAttribute(USER) : null;
145       User user = null;
146 
147       if (sessionUser == null ||
148           !sessionUser.getLogin().equals(auth.username))
149         user = (User)melati.getDatabase().getUserTable().getLoginColumn().
150                    firstWhereEq(auth.username);
151       else
152         user = sessionUser;
153 
154       if (user == null || !user.getPassword_unsafe().equals(auth.password)) {
155 
156         // Login/password authentication failed; we must trigger another
157         // attempt.  But do we know the "realm" (= POEM capability name) for
158         // which they were originally found not to be authorized?
159 
160         String storedRealm;
161         if (useSession() &&
162             (storedRealm = (String)melati.getSession().getAttribute(REALM))
163                  != null) {
164 
165           // The "realm" is stored in the session
166 
167           forceLogin(melati.getResponse(), storedRealm,
168                      "Login/password not recognised");
169           return null;
170         }
171         else {
172 
173           // We don't know the "realm", so we just let the user try again as
174           // `guest' and hopefully trigger the same problem and get the same
175           // message all over again.  Not very satisfactory but the alternative
176           // is providing a default realm like "<unknown>".
177 
178           PoemThread.setAccessToken(melati.getDatabase().guestAccessToken());
179           return melati;
180         }
181       }
182       else {
183 
184         // Login/password authentication succeeded
185 
186         PoemThread.setAccessToken(user);
187 
188         if (useSession() && user != sessionUser)
189           melati.getSession().setAttribute(USER, user);
190 
191         return melati;
192       }
193     }
194   }
195 
196   /**
197    * If we are allowed in then no need to change request.
198    *
199    * {@inheritDoc}
200    * @see org.melati.login.AccessHandler#buildRequest(org.melati.Melati)
201    */
202   public void buildRequest(Melati melati)
203       throws IOException {
204   }
205 }