View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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      public FormAuthModule(CallbackHandler callbackHandler, CrossContextPsuedoSession<UserInfo> ssoSource, 
96                            String loginPage, String errorPage)
97      {
98          super(callbackHandler);
99          this.ssoSource = ssoSource;
100         setLoginPage(loginPage);
101         setErrorPage(errorPage);
102     }
103 
104     @Override
105     public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, 
106                            CallbackHandler handler, Map options) 
107     throws AuthException
108     {
109         super.initialize(requestPolicy, responsePolicy, handler, options);
110         setLoginPage((String) options.get(LOGIN_PAGE_KEY));
111         setErrorPage((String) options.get(ERROR_PAGE_KEY));
112         ssoSource = (CrossContextPsuedoSession<UserInfo>) options.get(SSO_SOURCE_KEY);
113     }
114 
115     private void setLoginPage(String path)
116     {
117         if (!path.startsWith("/"))
118         {
119             LOG.warn("form-login-page must start with /");
120             path = "/" + path;
121         }
122         _formLoginPage = path;
123         _formLoginPath = path;
124         if (_formLoginPath.indexOf('?') > 0) _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
125     }
126 
127     /* ------------------------------------------------------------ */
128     private void setErrorPage(String path)
129     {
130         if (path == null || path.trim().length() == 0)
131         {
132             _formErrorPath = null;
133             _formErrorPage = null;
134         }
135         else
136         {
137             if (!path.startsWith("/"))
138             {
139                 LOG.warn("form-error-page must start with /");
140                 path = "/" + path;
141             }
142             _formErrorPage = path;
143             _formErrorPath = path;
144 
145             if (_formErrorPath.indexOf('?') > 0) _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
146         }
147     }
148 
149     @Override
150     public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
151     {
152        
153         HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
154         HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
155         String uri = request.getRequestURI();
156         if (uri==null)
157             uri=URIUtil.SLASH;
158         
159         boolean mandatory = isMandatory(messageInfo);  
160         mandatory |= isJSecurityCheck(uri);
161         HttpSession session = request.getSession(mandatory);
162         
163         // not mandatory or its the login or login error page don't authenticate
164         if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) return AuthStatus.SUCCESS;
165 
166         try
167         {
168             // Handle a request for authentication.
169             if (isJSecurityCheck(uri))
170             {
171                 final String username = request.getParameter(__J_USERNAME);
172                 final String password = request.getParameter(__J_PASSWORD);
173             
174                 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
175                 if (success)
176                 {
177                     // Redirect to original request                    
178                     String nuri=null;
179                     synchronized(session)
180                     {
181                         nuri = (String) session.getAttribute(__J_URI);
182                     }
183                     
184                     if (nuri == null || nuri.length() == 0)
185                     {
186                         nuri = request.getContextPath();
187                         if (nuri.length() == 0) 
188                             nuri = URIUtil.SLASH;
189                     }
190                    
191                     response.setContentLength(0);   
192                     response.sendRedirect(response.encodeRedirectURL(nuri));
193                     return AuthStatus.SEND_CONTINUE;
194                 }
195                 // not authenticated
196                 if (LOG.isDebugEnabled()) LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
197                 if (_formErrorPage == null)
198                 {
199                     if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
200                 }
201                 else
202                 {
203                     response.setContentLength(0);
204                     response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
205                 }
206                 // TODO is this correct response if isMandatory false??? Can
207                 // that occur?
208                 return AuthStatus.SEND_FAILURE;
209             }
210             
211             
212             // Check if the session is already authenticated.
213             SessionAuthentication sessionAuth = (SessionAuthentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
214             if (sessionAuth != null)
215             {                
216                 //TODO: ideally we would like the form auth module to be able to invoke the 
217                 //loginservice.validate() method to check the previously authed user, but it is not visible
218                 //to FormAuthModule
219                 if (sessionAuth.getUserIdentity().getSubject() == null)
220                     return AuthStatus.SEND_FAILURE;
221 
222                 Set<Object> credentials = sessionAuth.getUserIdentity().getSubject().getPrivateCredentials();
223                 if (credentials == null || credentials.isEmpty())
224                     return AuthStatus.SEND_FAILURE; //if no private credentials, assume it cannot be authenticated
225 
226                 clientSubject.getPrivateCredentials().addAll(credentials);
227                 clientSubject.getPrivateCredentials().add(sessionAuth.getUserIdentity());
228 
229                 return AuthStatus.SUCCESS;  
230             }
231             else if (ssoSource != null)
232             {
233                 UserInfo userInfo = ssoSource.fetch(request);
234                 if (userInfo != null)
235                 {
236                     boolean success = tryLogin(messageInfo, clientSubject, response, session, userInfo.getUserName(), new Password(new String(userInfo.getPassword())));
237                     if (success) { return AuthStatus.SUCCESS; }
238                 }
239             }
240             
241            
242 
243             // if we can't send challenge
244             if (DeferredAuthentication.isDeferred(response))
245                 return AuthStatus.SUCCESS; 
246             
247 
248             // redirect to login page  
249             StringBuffer buf = request.getRequestURL();
250             if (request.getQueryString() != null)
251                 buf.append("?").append(request.getQueryString());
252 
253             synchronized (session)
254             {
255                 session.setAttribute(__J_URI, buf.toString());
256             }
257             
258             response.setContentLength(0);
259             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
260             return AuthStatus.SEND_CONTINUE;
261         }
262         catch (IOException e)
263         {
264             throw new AuthException(e.getMessage());
265         }
266         catch (UnsupportedCallbackException e)
267         {
268             throw new AuthException(e.getMessage());
269         }
270 
271     }
272     
273     /* ------------------------------------------------------------ */
274     public boolean isJSecurityCheck(String uri)
275     {
276         int jsc = uri.indexOf(__J_SECURITY_CHECK);
277         
278         if (jsc<0)
279             return false;
280         int e=jsc+__J_SECURITY_CHECK.length();
281         if (e==uri.length())
282             return true;
283         char c = uri.charAt(e);
284         return c==';'||c=='#'||c=='/'||c=='?';
285     }
286 
287     private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, 
288                              HttpServletResponse response, HttpSession session, 
289                              String username, Password password) 
290     throws AuthException, IOException, UnsupportedCallbackException
291     {
292         if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
293         {
294             char[] pwdChars = password.toString().toCharArray();
295             Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
296            
297             if (!loginCallbacks.isEmpty())
298             {
299                 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
300                 Set<UserIdentity> userIdentities = clientSubject.getPrivateCredentials(UserIdentity.class);
301                 if (!userIdentities.isEmpty())
302                 {
303                     UserIdentity userIdentity = userIdentities.iterator().next();
304                    
305                 SessionAuthentication sessionAuth = new SessionAuthentication(Constraint.__FORM_AUTH, userIdentity, password);
306                 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, sessionAuth);
307                 }
308             }
309 
310             // Sign-on to SSO mechanism
311             if (ssoSource != null)
312             {
313                 UserInfo userInfo = new UserInfo(username, pwdChars);
314                 ssoSource.store(userInfo, response);
315             }
316             return true;
317         }
318         return false;
319     }
320 
321     public boolean isLoginOrErrorPage(String pathInContext)
322     {
323         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
324     }
325 
326 }