1   // ========================================================================
2   // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.security;
16  
17  import java.io.IOException;
18  import java.io.Serializable;
19  import java.security.Principal;
20  
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  import javax.servlet.http.HttpSession;
24  import javax.servlet.http.HttpSessionBindingEvent;
25  import javax.servlet.http.HttpSessionBindingListener;
26  
27  import org.mortbay.jetty.Authenticator;
28  import org.mortbay.jetty.Request;
29  import org.mortbay.jetty.Response;
30  import org.mortbay.jetty.UserRealm;
31  import org.mortbay.log.Log;
32  import org.mortbay.util.StringUtil;
33  import org.mortbay.util.URIUtil;
34  
35  
36  /* ------------------------------------------------------------ */
37  /** FORM Authentication Authenticator.
38   * The HTTP Session is used to store the authentication status of the
39   * user, which can be distributed.
40   * If the realm implements SSORealm, SSO is supported.
41   *
42   * @author Greg Wilkins (gregw)
43   * @author dan@greening.name
44   */
45  public class FormAuthenticator implements Authenticator
46  {
47      /* ------------------------------------------------------------ */
48      public final static String __J_URI="org.mortbay.jetty.URI";
49      public final static String __J_AUTHENTICATED="org.mortbay.jetty.Auth";
50      public final static String __J_SECURITY_CHECK="/j_security_check";
51      public final static String __J_USERNAME="j_username";
52      public final static String __J_PASSWORD="j_password";
53  
54      private String _formErrorPage;
55      private String _formErrorPath;
56      private String _formLoginPage;
57      private String _formLoginPath;
58      
59      /* ------------------------------------------------------------ */
60      public String getAuthMethod()
61      {
62          return HttpServletRequest.FORM_AUTH;
63      }
64  
65      /* ------------------------------------------------------------ */
66      public void setLoginPage(String path)
67      {
68          if (!path.startsWith("/"))
69          {
70              Log.warn("form-login-page must start with /");
71              path="/"+path;
72          }
73          _formLoginPage=path;
74          _formLoginPath=path;
75          if (_formLoginPath.indexOf('?')>0)
76              _formLoginPath=_formLoginPath.substring(0,_formLoginPath.indexOf('?'));
77      }
78  
79      /* ------------------------------------------------------------ */
80      public String getLoginPage()
81      {
82          return _formLoginPage;
83      }
84      
85      /* ------------------------------------------------------------ */
86      public void setErrorPage(String path)
87      {
88          if (path==null || path.trim().length()==0)
89          {
90              _formErrorPath=null;
91              _formErrorPage=null;
92          }
93          else
94          {
95              if (!path.startsWith("/"))
96              {
97                  Log.warn("form-error-page must start with /");
98                  path="/"+path;
99              }
100             _formErrorPage=path;
101             _formErrorPath=path;
102 
103             if (_formErrorPath!=null && _formErrorPath.indexOf('?')>0)
104                 _formErrorPath=_formErrorPath.substring(0,_formErrorPath.indexOf('?'));
105         }
106     }    
107 
108     /* ------------------------------------------------------------ */
109     public String getErrorPage()
110     {
111         return _formErrorPage;
112     }
113     
114     /* ------------------------------------------------------------ */
115     /** Perform form authentication.
116      * Called from SecurityHandler.
117      * @return UserPrincipal if authenticated else null.
118      */
119     public Principal authenticate(UserRealm realm,
120                                   String pathInContext,
121                                   Request request,
122                                   Response response)
123         throws IOException
124     {
125         // Handle paths
126         String uri = pathInContext;
127 
128         // Setup session 
129         HttpSession session=request.getSession(response!=null);
130         if (session==null)
131             return null;
132         
133         // Handle a request for authentication.
134         // TODO perhaps j_securitycheck can be uri suffix?
135         if ( uri.endsWith(__J_SECURITY_CHECK) )
136         {
137             // Check the session object for login info.
138             FormCredential form_cred=new FormCredential();
139             form_cred.authenticate(realm,
140                                             request.getParameter(__J_USERNAME),
141                                             request.getParameter(__J_PASSWORD),
142                                             request);
143             
144             String nuri=(String)session.getAttribute(__J_URI);
145             if (nuri==null || nuri.length()==0)
146             {
147                 nuri=request.getContextPath();
148                 if (nuri.length()==0)
149                     nuri=URIUtil.SLASH;
150             }
151             
152             if (form_cred._userPrincipal!=null)
153             {
154                 // Authenticated OK
155                 if(Log.isDebugEnabled())Log.debug("Form authentication OK for "+form_cred._jUserName);
156                 session.removeAttribute(__J_URI); // Remove popped return URI.
157                 request.setAuthType(Constraint.__FORM_AUTH);
158                 request.setUserPrincipal(form_cred._userPrincipal);
159                 session.setAttribute(__J_AUTHENTICATED,form_cred);
160 
161                 // Sign-on to SSO mechanism
162                 if (realm instanceof SSORealm)
163                     ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new Password(form_cred._jPassword));
164 
165                 // Redirect to original request
166                 if (response != null) {
167                     response.setContentLength(0);
168                     response.sendRedirect(response.encodeRedirectURL(nuri));
169                 }
170             }   
171             else
172             {
173                 if(Log.isDebugEnabled())Log.debug("Form authentication FAILED for "+StringUtil.printable(form_cred._jUserName));
174                 if (_formErrorPage==null)
175                 {
176                     if (response != null) 
177                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
178                 }
179                 else
180                 {
181                     if (response != null)
182                         response.setContentLength(0);
183                         response.sendRedirect(response.encodeRedirectURL
184                                           (URIUtil.addPaths(request.getContextPath(),
185                                                         _formErrorPage)));
186                 }
187             }
188             
189             // Security check is always false, only true after final redirection.
190             return null;
191         }
192         
193         // Check if the session is already authenticated.
194         FormCredential form_cred = (FormCredential) session.getAttribute(__J_AUTHENTICATED);
195         
196         if (form_cred != null)
197         {
198             // We have a form credential. Has it been distributed?
199             if (form_cred._userPrincipal==null)
200             {
201                 // This form_cred appears to have been distributed.  Need to reauth
202                 form_cred.authenticate(realm, request);
203                 
204                 // Sign-on to SSO mechanism
205                 if (form_cred._userPrincipal!=null && realm instanceof SSORealm)
206                     ((SSORealm)realm).setSingleSignOn(request,response,form_cred._userPrincipal,new Password(form_cred._jPassword));
207                 
208             }
209             else if (!realm.reauthenticate(form_cred._userPrincipal))
210                 // Else check that it is still authenticated.
211                 form_cred._userPrincipal=null;
212 
213             // If this credential is still authenticated
214             if (form_cred._userPrincipal!=null)
215             {
216                 if(Log.isDebugEnabled())Log.debug("FORM Authenticated for "+form_cred._userPrincipal.getName());
217                 request.setAuthType(Constraint.__FORM_AUTH);
218                 request.setUserPrincipal(form_cred._userPrincipal);
219                 return form_cred._userPrincipal;
220             }
221             else
222                 session.setAttribute(__J_AUTHENTICATED,null);
223         }
224         else if (realm instanceof SSORealm)
225         {
226             // Try a single sign on.
227             Credential cred = ((SSORealm)realm).getSingleSignOn(request,response);
228             
229             if (request.getUserPrincipal()!=null)
230             {
231                 form_cred=new FormCredential();
232                 form_cred._userPrincipal=request.getUserPrincipal();
233                 form_cred._jUserName=form_cred._userPrincipal.getName();
234                 if (cred!=null)
235                     form_cred._jPassword=cred.toString();
236                 if(Log.isDebugEnabled())Log.debug("SSO for "+form_cred._userPrincipal);
237                            
238                 request.setAuthType(Constraint.__FORM_AUTH);
239                 session.setAttribute(__J_AUTHENTICATED,form_cred);
240                 return form_cred._userPrincipal;
241             }
242         }
243         
244         // Don't authenticate authform or errorpage
245         if (isLoginOrErrorPage(pathInContext))
246             return UserRealm.NOBODY;
247         
248         // redirect to login page
249         if (response!=null)
250         {
251             if (request.getQueryString()!=null)
252                 uri+="?"+request.getQueryString();
253             session.setAttribute(__J_URI, 
254                                  request.getScheme() +
255                                  "://" + request.getServerName() +
256                                  ":" + request.getServerPort() +
257                                  URIUtil.addPaths(request.getContextPath(),uri));
258             response.setContentLength(0);
259             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),
260                                                                           _formLoginPage)));
261         }
262 
263         return null;
264     }
265 
266     public boolean isLoginOrErrorPage(String pathInContext)
267     {
268         return pathInContext!=null &&
269          (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
270     }
271     
272     /* ------------------------------------------------------------ */
273     /** FORM Authentication credential holder.
274      */
275     private static class FormCredential implements Serializable, HttpSessionBindingListener
276     {
277         String _jUserName;
278         String _jPassword;
279         transient Principal _userPrincipal;
280         transient UserRealm _realm;
281 
282         void authenticate(UserRealm realm,String user,String password,Request request)
283         {
284             _jUserName=user;
285             _jPassword=password;
286             _userPrincipal = realm.authenticate(user, password, request);
287             if (_userPrincipal!=null)
288                 _realm=realm;
289             else
290             {
291                 Log.warn("AUTH FAILURE: user {}",StringUtil.printable(user));
292                 request.setUserPrincipal(null);
293             }
294         }
295 
296         void authenticate(UserRealm realm,Request request)
297         {
298             _userPrincipal = realm.authenticate(_jUserName, _jPassword, request);
299             if (_userPrincipal!=null)
300                 _realm=realm;
301             else
302             {
303                 Log.warn("AUTH FAILURE: user {}",StringUtil.printable(_jUserName));
304                 request.setUserPrincipal(null);
305             }
306         }
307         
308 
309         public void valueBound(HttpSessionBindingEvent event) {}
310         
311         public void valueUnbound(HttpSessionBindingEvent event)
312         {
313             if(Log.isDebugEnabled())Log.debug("Logout "+_jUserName);
314             
315             if(_realm instanceof SSORealm)
316                 ((SSORealm)_realm).clearSingleSignOn(_jUserName);
317                
318             if(_realm!=null && _userPrincipal!=null)
319                 _realm.logout(_userPrincipal); 
320         }
321         
322         public int hashCode()
323         {
324             return _jUserName.hashCode()+_jPassword.hashCode();
325         }
326 
327         public boolean equals(Object o)
328         {
329             if (!(o instanceof FormCredential))
330                 return false;
331             FormCredential fc = (FormCredential)o;
332             return
333                 _jUserName.equals(fc._jUserName) &&
334                 _jPassword.equals(fc._jPassword);
335         }
336 
337         public String toString()
338         {
339             return "Cred["+_jUserName+"]";
340         }
341 
342     }
343 }