View Javadoc

1   // ========================================================================
2   // Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.security.jaspi.modules;
15  
16  import java.io.IOException;
17  import java.io.Serializable;
18  import java.security.Principal;
19  import java.util.Arrays;
20  import java.util.Map;
21  import java.util.Set;
22  
23  import javax.security.auth.Subject;
24  import javax.security.auth.callback.CallbackHandler;
25  import javax.security.auth.callback.UnsupportedCallbackException;
26  import javax.security.auth.message.AuthException;
27  import javax.security.auth.message.AuthStatus;
28  import javax.security.auth.message.MessageInfo;
29  import javax.security.auth.message.MessagePolicy;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.servlet.http.HttpSession;
33  import javax.servlet.http.HttpSessionBindingEvent;
34  import javax.servlet.http.HttpSessionBindingListener;
35  
36  import org.eclipse.jetty.http.security.Constraint;
37  import org.eclipse.jetty.http.security.Password;
38  import org.eclipse.jetty.security.CrossContextPsuedoSession;
39  import org.eclipse.jetty.security.authentication.LoginCallbackImpl;
40  import org.eclipse.jetty.util.StringUtil;
41  import org.eclipse.jetty.util.URIUtil;
42  import org.eclipse.jetty.util.log.Log;
43  
44  /**
45   * @deprecated use *ServerAuthentication
46   * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
47   */
48  public class FormAuthModule extends BaseAuthModule
49  {
50      /* ------------------------------------------------------------ */
51      public final static String __J_URI = "org.eclipse.jetty.util.URI";
52  
53      public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth";
54  
55      public final static String __J_SECURITY_CHECK = "/j_security_check";
56  
57      public final static String __J_USERNAME = "j_username";
58  
59      public final static String __J_PASSWORD = "j_password";
60  
61      // private String realmName;
62      public static final String LOGIN_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.LoginPage";
63  
64      public static final String ERROR_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.ErrorPage";
65  
66      public static final String SSO_SOURCE_KEY = "org.eclipse.jetty.security.jaspi.modules.SsoSource";
67  
68      private String _formErrorPage;
69  
70      private String _formErrorPath;
71  
72      private String _formLoginPage;
73  
74      private String _formLoginPath;
75  
76      private CrossContextPsuedoSession<UserInfo> ssoSource;
77  
78      public FormAuthModule()
79      {
80      }
81  
82      public FormAuthModule(CallbackHandler callbackHandler, String loginPage, String errorPage)
83      {
84          super(callbackHandler);
85          setLoginPage(loginPage);
86          setErrorPage(errorPage);
87      }
88  
89      public FormAuthModule(CallbackHandler callbackHandler, CrossContextPsuedoSession<UserInfo> ssoSource, 
90                            String loginPage, String errorPage)
91      {
92          super(callbackHandler);
93          this.ssoSource = ssoSource;
94          setLoginPage(loginPage);
95          setErrorPage(errorPage);
96      }
97  
98      @Override
99      public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, 
100                            CallbackHandler handler, Map options) 
101     throws AuthException
102     {
103         super.initialize(requestPolicy, responsePolicy, handler, options);
104         setLoginPage((String) options.get(LOGIN_PAGE_KEY));
105         setErrorPage((String) options.get(ERROR_PAGE_KEY));
106         ssoSource = (CrossContextPsuedoSession<UserInfo>) options.get(SSO_SOURCE_KEY);
107     }
108 
109     private void setLoginPage(String path)
110     {
111         if (!path.startsWith("/"))
112         {
113             Log.warn("form-login-page must start with /");
114             path = "/" + path;
115         }
116         _formLoginPage = path;
117         _formLoginPath = path;
118         if (_formLoginPath.indexOf('?') > 0) _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
119     }
120 
121     /* ------------------------------------------------------------ */
122     private void setErrorPage(String path)
123     {
124         if (path == null || path.trim().length() == 0)
125         {
126             _formErrorPath = null;
127             _formErrorPage = null;
128         }
129         else
130         {
131             if (!path.startsWith("/"))
132             {
133                 Log.warn("form-error-page must start with /");
134                 path = "/" + path;
135             }
136             _formErrorPage = path;
137             _formErrorPath = path;
138 
139             if (_formErrorPath.indexOf('?') > 0) _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
140         }
141     }
142 
143     @Override
144     public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
145     {
146         HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
147         HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
148         HttpSession session = request.getSession(isMandatory(messageInfo));
149         String uri = request.getPathInfo();
150         // not mandatory and not authenticated
151         if (session == null || isLoginOrErrorPage(uri)) return AuthStatus.SUCCESS;
152 
153         try
154         {
155             // Handle a request for authentication.
156             // TODO perhaps j_securitycheck can be uri suffix?
157             if (uri.endsWith(__J_SECURITY_CHECK))
158             {
159 
160                 final String username = request.getParameter(__J_USERNAME);
161                 final String password = request.getParameter(__J_PASSWORD);
162                 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
163                 if (success)
164                 {
165                     // Redirect to original request
166                     String nuri = (String) session.getAttribute(__J_URI);
167                     if (nuri == null || nuri.length() == 0)
168                     {
169                         nuri = request.getContextPath();
170                         if (nuri.length() == 0) nuri = URIUtil.SLASH;
171                     }
172                     session.removeAttribute(__J_URI); // Remove popped return
173                                                         // URI.
174                     response.setContentLength(0);
175                     response.sendRedirect(response.encodeRedirectURL(nuri));
176 
177                     return AuthStatus.SEND_CONTINUE;
178                 }
179                 // not authenticated
180                 if (Log.isDebugEnabled()) Log.debug("Form authentication FAILED for " + StringUtil.printable(username));
181                 if (_formErrorPage == null)
182                 {
183                     if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
184                 }
185                 else
186                 {
187                     response.setContentLength(0);
188                     response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
189                 }
190                 // TODO is this correct response if isMandatory false??? Can
191                 // that occur?
192                 return AuthStatus.SEND_FAILURE;
193             }
194             // Check if the session is already authenticated.
195             FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED);
196 
197             if (form_cred != null)
198             {
199                 boolean success = tryLogin(messageInfo, clientSubject, response, session, form_cred._jUserName, new Password(new String(form_cred._jPassword)));
200                 if (success) { return AuthStatus.SUCCESS; }
201                 // CallbackHandler loginCallbackHandler = new
202                 // UserPasswordCallbackHandler(form_cred._jUserName,
203                 // form_cred._jPassword);
204                 // LoginResult loginResult = loginService.login(clientSubject,
205                 // loginCallbackHandler);
206                 // //TODO what should happen if !isMandatory but credentials
207                 // exist and are wrong?
208                 // if (loginResult.isSuccess())
209                 // {
210                 // callbackHandler.handle(new
211                 // Callback[]{loginResult.getCallerPrincipalCallback(),
212                 // loginResult.getGroupPrincipalCallback()});
213                 // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY,
214                 // Constraint.__FORM_AUTH);
215                 //
216                 // form_cred = new FormCredential(form_cred._jUserName,
217                 // form_cred._jPassword,
218                 // loginResult.getCallerPrincipalCallback().getPrincipal());
219                 //
220                 // session.setAttribute(__J_AUTHENTICATED, form_cred);
221                 // if (ssoSource != null && ssoSource.fetch(request) == null)
222                 // {
223                 // UserInfo userInfo = new UserInfo(form_cred._jUserName,
224                 // form_cred._jPassword);
225                 // ssoSource.store(userInfo, response);
226                 // }
227                 // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY,
228                 // Constraint.__FORM_AUTH);
229                 // return AuthStatus.SUCCESS;
230                 // }
231 
232                 // // We have a form credential. Has it been distributed?
233                 // if (form_cred._userPrincipal==null)
234                 // {
235                 // // This form_cred appears to have been distributed. Need to
236                 // reauth
237                 // form_cred.authenticate(realm, request);
238                 //
239                 // // Sign-on to SSO mechanism
240                 // if (form_cred._userPrincipal!=null && realm instanceof
241                 // SSORealm)
242                 // ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new
243                 // Password(form_cred._jPassword));
244                 //
245                 // }
246                 // else if (!realm.reauthenticate(form_cred._userPrincipal))
247                 // // Else check that it is still authenticated.
248                 // form_cred._userPrincipal=null;
249                 //
250                 // // If this credential is still authenticated
251                 // if (form_cred._userPrincipal!=null)
252                 // {
253                 // if(Log.isDebugEnabled())Log.debug("FORM Authenticated for
254                 // "+form_cred._userPrincipal.getName());
255                 // request.setAuthType(Constraint.__FORM_AUTH);
256                 // //jaspi
257                 // // request.setUserPrincipal(form_cred._userPrincipal);
258                 // return form_cred._userPrincipal;
259                 // }
260                 // else
261                 // session.setAttribute(__J_AUTHENTICATED,null);
262                 // }
263                 // else if (realm instanceof SSORealm)
264                 // {
265                 // // Try a single sign on.
266                 // Credential cred =
267                 // ((SSORealm)realm).getSingleSignOn(request,response);
268                 //
269                 // if (request.getUserPrincipal()!=null)
270                 // {
271                 // form_cred=new FormCredential();
272                 // form_cred._userPrincipal=request.getUserPrincipal();
273                 // form_cred._jUserName=form_cred._userPrincipal.getName();
274                 // if (cred!=null)
275                 // form_cred._jPassword=cred.toString();
276                 // if(Log.isDebugEnabled())Log.debug("SSO for
277                 // "+form_cred._userPrincipal);
278                 //
279                 // request.setAuthType(Constraint.__FORM_AUTH);
280                 // session.setAttribute(__J_AUTHENTICATED,form_cred);
281                 // return form_cred._userPrincipal;
282                 // }
283             }
284             else if (ssoSource != null)
285             {
286                 UserInfo userInfo = ssoSource.fetch(request);
287                 if (userInfo != null)
288                 {
289                     boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
290                     if (success) { return AuthStatus.SUCCESS; }
291                 }
292             }
293 
294             // Don't authenticate authform or errorpage
295             if (!isMandatory(messageInfo) || isLoginOrErrorPage(uri))
296             // TODO verify this is correct action
297                 return AuthStatus.SUCCESS;
298 
299             // redirect to login page
300             if (request.getQueryString() != null) uri += "?" + request.getQueryString();
301             session.setAttribute(__J_URI, request.getScheme() + "://"
302                                           + request.getServerName()
303                                           + ":"
304                                           + request.getServerPort()
305                                           + URIUtil.addPaths(request.getContextPath(), uri));
306             response.setContentLength(0);
307             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
308             return AuthStatus.SEND_CONTINUE;
309         }
310         catch (IOException e)
311         {
312             throw new AuthException(e.getMessage());
313         }
314         catch (UnsupportedCallbackException e)
315         {
316             throw new AuthException(e.getMessage());
317         }
318 
319     }
320 
321     private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, 
322                              HttpServletResponse response, HttpSession session, 
323                              String username, Password password) 
324     throws AuthException, IOException, UnsupportedCallbackException
325     {
326         if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
327         {
328             char[] pwdChars = password.toString().toCharArray();
329             Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
330             if (!loginCallbacks.isEmpty())
331             {
332                 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
333                 FormCredential form_cred = new FormCredential(username, pwdChars, loginCallback.getUserPrincipal());
334 
335                 session.setAttribute(__J_AUTHENTICATED, form_cred);
336             }
337 
338             // Sign-on to SSO mechanism
339             if (ssoSource != null)
340             {
341                 UserInfo userInfo = new UserInfo(username, pwdChars);
342                 ssoSource.store(userInfo, response);
343             }
344             return true;
345         }
346         return false;
347         // LoginCallback loginCallback = new LoginCallback(clientSubject,
348         // username, password);
349         // loginService.login(loginCallback);
350         // if (loginCallback.isSuccess())
351         // {
352         // CallerPrincipalCallback callerPrincipalCallback = new
353         // CallerPrincipalCallback(clientSubject,
354         // loginCallback.getUserPrincipal());
355         // GroupPrincipalCallback groupPrincipalCallback = new
356         // GroupPrincipalCallback(clientSubject,
357         // loginCallback.getGroups().toArray(new
358         // String[loginCallback.getGroups().size()]));
359         // callbackHandler.handle(new Callback[] {callerPrincipalCallback,
360         // groupPrincipalCallback});
361         // messageInfo.getMap().put(JettyMessageInfo.AUTH_METHOD_KEY,
362         // Constraint.__FORM_AUTH);
363         // FormCredential form_cred = new FormCredential(username, password,
364         // loginCallback.getUserPrincipal());
365         //
366         // session.setAttribute(__J_AUTHENTICATED, form_cred);
367         // // Sign-on to SSO mechanism
368         // if (ssoSource != null)
369         // {
370         // UserInfo userInfo = new UserInfo(username, password);
371         // ssoSource.store(userInfo, response);
372         // }
373         // }
374         // return loginCallback.isSuccess();
375     }
376 
377     public boolean isLoginOrErrorPage(String pathInContext)
378     {
379         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
380     }
381 
382     /* ------------------------------------------------------------ */
383     /**
384      * FORM Authentication credential holder.
385      */
386     private static class FormCredential implements Serializable, HttpSessionBindingListener
387     {
388         String _jUserName;
389 
390         char[] _jPassword;
391 
392         transient Principal _userPrincipal;
393 
394         private FormCredential(String _jUserName, char[] _jPassword, Principal _userPrincipal)
395         {
396             this._jUserName = _jUserName;
397             this._jPassword = _jPassword;
398             this._userPrincipal = _userPrincipal;
399         }
400 
401         public void valueBound(HttpSessionBindingEvent event)
402         {
403         }
404 
405         public void valueUnbound(HttpSessionBindingEvent event)
406         {
407             if (Log.isDebugEnabled()) Log.debug("Logout " + _jUserName);
408 
409             // TODO jaspi call cleanSubject()
410             // if (_realm instanceof SSORealm)
411             // ((SSORealm) _realm).clearSingleSignOn(_jUserName);
412             //
413             // if (_realm != null && _userPrincipal != null)
414             // _realm.logout(_userPrincipal);
415         }
416 
417         public int hashCode()
418         {
419             return _jUserName.hashCode() + _jPassword.hashCode();
420         }
421 
422         public boolean equals(Object o)
423         {
424             if (!(o instanceof FormCredential)) return false;
425             FormCredential fc = (FormCredential) o;
426             return _jUserName.equals(fc._jUserName) && Arrays.equals(_jPassword, fc._jPassword);
427         }
428 
429         public String toString()
430         {
431             return "Cred[" + _jUserName + "]";
432         }
433 
434     }
435 
436 }