View Javadoc

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