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