View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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()))) 
168             return AuthStatus.SUCCESS;  // TODO return null for do nothing?
169 
170         try
171         {
172             // Handle a request for authentication.
173             if (isJSecurityCheck(uri))
174             {
175                 final String username = request.getParameter(__J_USERNAME);
176                 final String password = request.getParameter(__J_PASSWORD);
177             
178                 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
179                 if (success)
180                 {
181                     // Redirect to original request                    
182                     String nuri=null;
183                     synchronized(session)
184                     {
185                         nuri = (String) session.getAttribute(__J_URI);
186                     }
187                     
188                     if (nuri == null || nuri.length() == 0)
189                     {
190                         nuri = request.getContextPath();
191                         if (nuri.length() == 0) 
192                             nuri = URIUtil.SLASH;
193                     }
194                    
195                     response.setContentLength(0);   
196                     response.sendRedirect(response.encodeRedirectURL(nuri));
197                     return AuthStatus.SEND_CONTINUE;
198                 }
199                 // not authenticated
200                 if (LOG.isDebugEnabled()) LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
201                 if (_formErrorPage == null)
202                 {
203                     if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
204                 }
205                 else
206                 {
207                     response.setContentLength(0);
208                     response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
209                 }
210                 // TODO is this correct response if isMandatory false??? Can
211                 // that occur?
212                 return AuthStatus.SEND_FAILURE;
213             }
214             
215             
216             // Check if the session is already authenticated.
217             SessionAuthentication sessionAuth = (SessionAuthentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
218             if (sessionAuth != null)
219             {                
220                 //TODO: ideally we would like the form auth module to be able to invoke the 
221                 //loginservice.validate() method to check the previously authed user, but it is not visible
222                 //to FormAuthModule
223                 if (sessionAuth.getUserIdentity().getSubject() == null)
224                     return AuthStatus.SEND_FAILURE;
225 
226                 Set<Object> credentials = sessionAuth.getUserIdentity().getSubject().getPrivateCredentials();
227                 if (credentials == null || credentials.isEmpty())
228                     return AuthStatus.SEND_FAILURE; //if no private credentials, assume it cannot be authenticated
229 
230                 clientSubject.getPrivateCredentials().addAll(credentials);
231                 clientSubject.getPrivateCredentials().add(sessionAuth.getUserIdentity());
232 
233                 return AuthStatus.SUCCESS;  
234             }
235             else if (ssoSource != null)
236             {
237                 UserInfo userInfo = ssoSource.fetch(request);
238                 if (userInfo != null)
239                 {
240                     boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
241                     if (success) { return AuthStatus.SUCCESS; }
242                 }
243             }
244             
245            
246 
247             // if we can't send challenge
248             if (DeferredAuthentication.isDeferred(response))
249                 return AuthStatus.SUCCESS; 
250             
251 
252             // redirect to login page  
253             StringBuffer buf = request.getRequestURL();
254             if (request.getQueryString() != null)
255                 buf.append("?").append(request.getQueryString());
256 
257             synchronized (session)
258             {
259                 session.setAttribute(__J_URI, buf.toString());
260             }
261             
262             response.setContentLength(0);
263             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
264             return AuthStatus.SEND_CONTINUE;
265         }
266         catch (IOException e)
267         {
268             throw new AuthException(e.getMessage());
269         }
270         catch (UnsupportedCallbackException e)
271         {
272             throw new AuthException(e.getMessage());
273         }
274 
275     }
276     
277     /* ------------------------------------------------------------ */
278     public boolean isJSecurityCheck(String uri)
279     {
280         int jsc = uri.indexOf(__J_SECURITY_CHECK);
281         
282         if (jsc<0)
283             return false;
284         int e=jsc+__J_SECURITY_CHECK.length();
285         if (e==uri.length())
286             return true;
287         char c = uri.charAt(e);
288         return c==';'||c=='#'||c=='/'||c=='?';
289     }
290 
291     private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, 
292                              HttpServletResponse response, HttpSession session, 
293                              String username, Password password) 
294     throws AuthException, IOException, UnsupportedCallbackException
295     {
296         if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
297         {
298             char[] pwdChars = password.toString().toCharArray();
299             Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
300            
301             if (!loginCallbacks.isEmpty())
302             {
303                 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
304                 Set<UserIdentity> userIdentities = clientSubject.getPrivateCredentials(UserIdentity.class);
305                 if (!userIdentities.isEmpty())
306                 {
307                     UserIdentity userIdentity = userIdentities.iterator().next();
308                    
309                 SessionAuthentication sessionAuth = new SessionAuthentication(Constraint.__FORM_AUTH, userIdentity, password);
310                 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, sessionAuth);
311                 }
312             }
313 
314             // Sign-on to SSO mechanism
315             if (ssoSource != null)
316             {
317                 UserInfo userInfo = new UserInfo(username, pwdChars);
318                 ssoSource.store(userInfo, response);
319             }
320             return true;
321         }
322         return false;
323     }
324 
325     public boolean isLoginOrErrorPage(String pathInContext)
326     {
327         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
328     }
329 
330 }