View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.security.jaspi.modules;
20  
21  import java.io.IOException;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import javax.security.auth.Subject;
26  import javax.security.auth.callback.CallbackHandler;
27  import javax.security.auth.callback.UnsupportedCallbackException;
28  import javax.security.auth.message.AuthException;
29  import javax.security.auth.message.AuthStatus;
30  import javax.security.auth.message.MessageInfo;
31  import javax.security.auth.message.MessagePolicy;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  import javax.servlet.http.HttpSession;
35  
36  import org.eclipse.jetty.security.CrossContextPsuedoSession;
37  import org.eclipse.jetty.security.authentication.DeferredAuthentication;
38  import org.eclipse.jetty.security.authentication.LoginCallbackImpl;
39  import org.eclipse.jetty.security.authentication.SessionAuthentication;
40  import org.eclipse.jetty.server.UserIdentity;
41  import org.eclipse.jetty.util.StringUtil;
42  import org.eclipse.jetty.util.URIUtil;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  import org.eclipse.jetty.util.security.Constraint;
46  import org.eclipse.jetty.util.security.Password;
47  
48  /**
49   * @deprecated use *ServerAuthentication
50   * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
51   */
52  public class FormAuthModule extends BaseAuthModule
53  {
54      private static final Logger LOG = Log.getLogger(FormAuthModule.class);
55  
56      /* ------------------------------------------------------------ */
57      public final static String __J_URI = "org.eclipse.jetty.util.URI";
58  
59      public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth";
60  
61      public final static String __J_SECURITY_CHECK = "/j_security_check";
62  
63      public final static String __J_USERNAME = "j_username";
64  
65      public final static String __J_PASSWORD = "j_password";
66  
67      // private String realmName;
68      public static final String LOGIN_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.LoginPage";
69  
70      public static final String ERROR_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.ErrorPage";
71  
72      public static final String SSO_SOURCE_KEY = "org.eclipse.jetty.security.jaspi.modules.SsoSource";
73  
74      private String _formErrorPage;
75  
76      private String _formErrorPath;
77  
78      private String _formLoginPage;
79  
80      private String _formLoginPath;
81  
82      private CrossContextPsuedoSession<UserInfo> ssoSource;
83  
84      public FormAuthModule()
85      {
86      }
87  
88      public FormAuthModule(CallbackHandler callbackHandler, String loginPage, String errorPage)
89      {
90          super(callbackHandler);
91          setLoginPage(loginPage);
92          setErrorPage(errorPage);
93      }
94  
95      /**
96       * @deprecated
97       */
98      public FormAuthModule(CallbackHandler callbackHandler, CrossContextPsuedoSession<UserInfo> ssoSource, 
99                            String loginPage, String errorPage)
100     {
101         super(callbackHandler);
102         this.ssoSource = ssoSource;
103         setLoginPage(loginPage);
104         setErrorPage(errorPage);
105     }
106 
107     @Override
108     public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, 
109                            CallbackHandler handler, Map options) 
110     throws AuthException
111     {
112         super.initialize(requestPolicy, responsePolicy, handler, options);
113         setLoginPage((String) options.get(LOGIN_PAGE_KEY));
114         setErrorPage((String) options.get(ERROR_PAGE_KEY));
115         ssoSource = (CrossContextPsuedoSession<UserInfo>) options.get(SSO_SOURCE_KEY);
116     }
117 
118     private void setLoginPage(String path)
119     {
120         if (!path.startsWith("/"))
121         {
122             LOG.warn("form-login-page must start with /");
123             path = "/" + path;
124         }
125         _formLoginPage = path;
126         _formLoginPath = path;
127         if (_formLoginPath.indexOf('?') > 0) _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
128     }
129 
130     /* ------------------------------------------------------------ */
131     private void setErrorPage(String path)
132     {
133         if (path == null || path.trim().length() == 0)
134         {
135             _formErrorPath = null;
136             _formErrorPage = null;
137         }
138         else
139         {
140             if (!path.startsWith("/"))
141             {
142                 LOG.warn("form-error-page must start with /");
143                 path = "/" + path;
144             }
145             _formErrorPage = path;
146             _formErrorPath = path;
147 
148             if (_formErrorPath.indexOf('?') > 0) _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
149         }
150     }
151 
152     @Override
153     public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
154     {
155        
156         HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
157         HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
158         String uri = request.getRequestURI();
159         if (uri==null)
160             uri=URIUtil.SLASH;
161         
162         boolean mandatory = isMandatory(messageInfo);  
163         mandatory |= isJSecurityCheck(uri);
164         HttpSession session = request.getSession(mandatory);
165         
166         // not mandatory or its the login or login error page don't authenticate
167         if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) return AuthStatus.SUCCESS;
168 
169         try
170         {
171             // Handle a request for authentication.
172             if (isJSecurityCheck(uri))
173             {
174                 final String username = request.getParameter(__J_USERNAME);
175                 final String password = request.getParameter(__J_PASSWORD);
176             
177                 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
178                 if (success)
179                 {
180                     // Redirect to original request                    
181                     String nuri=null;
182                     synchronized(session)
183                     {
184                         nuri = (String) session.getAttribute(__J_URI);
185                     }
186                     
187                     if (nuri == null || nuri.length() == 0)
188                     {
189                         nuri = request.getContextPath();
190                         if (nuri.length() == 0) 
191                             nuri = URIUtil.SLASH;
192                     }
193                    
194                     response.setContentLength(0);   
195                     response.sendRedirect(response.encodeRedirectURL(nuri));
196                     return AuthStatus.SEND_CONTINUE;
197                 }
198                 // not authenticated
199                 if (LOG.isDebugEnabled()) LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
200                 if (_formErrorPage == null)
201                 {
202                     if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
203                 }
204                 else
205                 {
206                     response.setContentLength(0);
207                     response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
208                 }
209                 // TODO is this correct response if isMandatory false??? Can
210                 // that occur?
211                 return AuthStatus.SEND_FAILURE;
212             }
213             
214             
215             // Check if the session is already authenticated.
216             SessionAuthentication sessionAuth = (SessionAuthentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
217             if (sessionAuth != null)
218             {                
219                 //TODO: ideally we would like the form auth module to be able to invoke the 
220                 //loginservice.validate() method to check the previously authed user, but it is not visible
221                 //to FormAuthModule
222                 if (sessionAuth.getUserIdentity().getSubject() == null)
223                     return AuthStatus.SEND_FAILURE;
224 
225                 Set<Object> credentials = sessionAuth.getUserIdentity().getSubject().getPrivateCredentials();
226                 if (credentials == null || credentials.isEmpty())
227                     return AuthStatus.SEND_FAILURE; //if no private credentials, assume it cannot be authenticated
228 
229                 clientSubject.getPrivateCredentials().addAll(credentials);
230                 clientSubject.getPrivateCredentials().add(sessionAuth.getUserIdentity());
231 
232                 return AuthStatus.SUCCESS;  
233             }
234             else if (ssoSource != null)
235             {
236                 UserInfo userInfo = ssoSource.fetch(request);
237                 if (userInfo != null)
238                 {
239                     boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
240                     if (success) { return AuthStatus.SUCCESS; }
241                 }
242             }
243             
244            
245 
246             // if we can't send challenge
247             if (DeferredAuthentication.isDeferred(response))
248                 return AuthStatus.SUCCESS; 
249             
250 
251             // redirect to login page  
252             StringBuffer buf = request.getRequestURL();
253             if (request.getQueryString() != null)
254                 buf.append("?").append(request.getQueryString());
255 
256             synchronized (session)
257             {
258                 session.setAttribute(__J_URI, buf.toString());
259             }
260             
261             response.setContentLength(0);
262             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
263             return AuthStatus.SEND_CONTINUE;
264         }
265         catch (IOException e)
266         {
267             throw new AuthException(e.getMessage());
268         }
269         catch (UnsupportedCallbackException e)
270         {
271             throw new AuthException(e.getMessage());
272         }
273 
274     }
275     
276     /* ------------------------------------------------------------ */
277     public boolean isJSecurityCheck(String uri)
278     {
279         int jsc = uri.indexOf(__J_SECURITY_CHECK);
280         
281         if (jsc<0)
282             return false;
283         int e=jsc+__J_SECURITY_CHECK.length();
284         if (e==uri.length())
285             return true;
286         char c = uri.charAt(e);
287         return c==';'||c=='#'||c=='/'||c=='?';
288     }
289 
290     private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, 
291                              HttpServletResponse response, HttpSession session, 
292                              String username, Password password) 
293     throws AuthException, IOException, UnsupportedCallbackException
294     {
295         if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
296         {
297             char[] pwdChars = password.toString().toCharArray();
298             Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
299            
300             if (!loginCallbacks.isEmpty())
301             {
302                 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
303                 Set<UserIdentity> userIdentities = clientSubject.getPrivateCredentials(UserIdentity.class);
304                 if (!userIdentities.isEmpty())
305                 {
306                     UserIdentity userIdentity = userIdentities.iterator().next();
307                    
308                 SessionAuthentication sessionAuth = new SessionAuthentication(Constraint.__FORM_AUTH, userIdentity, password);
309                 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, sessionAuth);
310                 }
311             }
312 
313             // Sign-on to SSO mechanism
314             if (ssoSource != null)
315             {
316                 UserInfo userInfo = new UserInfo(username, pwdChars);
317                 ssoSource.store(userInfo, response);
318             }
319             return true;
320         }
321         return false;
322     }
323 
324     public boolean isLoginOrErrorPage(String pathInContext)
325     {
326         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
327     }
328 
329 }