KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jcorporate > expresso > services > controller > LoginController


1 /* ====================================================================
2  * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
3  *
4  * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  *
18  * 3. The end-user documentation included with the redistribution,
19  * if any, must include the following acknowledgment:
20  * "This product includes software developed by Jcorporate Ltd.
21  * (http://www.jcorporate.com/)."
22  * Alternately, this acknowledgment may appear in the software itself,
23  * if and wherever such third-party acknowledgments normally appear.
24  *
25  * 4. "Jcorporate" and product names such as "Expresso" must
26  * not be used to endorse or promote products derived from this
27  * software without prior written permission. For written permission,
28  * please contact info@jcorporate.com.
29  *
30  * 5. Products derived from this software may not be called "Expresso",
31  * or other Jcorporate product names; nor may "Expresso" or other
32  * Jcorporate product names appear in their name, without prior
33  * written permission of Jcorporate Ltd.
34  *
35  * 6. No product derived from this software may compete in the same
36  * market space, i.e. framework, without prior written permission
37  * of Jcorporate Ltd. For written permission, please contact
38  * partners@jcorporate.com.
39  *
40  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
41  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
46  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This software consists of voluntary contributions made by many
55  * individuals on behalf of the Jcorporate Ltd. Contributions back
56  * to the project(s) are encouraged when you make modifications.
57  * Please send them to support@jcorporate.com. For more information
58  * on Jcorporate Ltd. and its products, please see
59  * <http://www.jcorporate.com/>.
60  *
61  * Portions of this software are based upon other open source
62  * products and are subject to their respective licenses.
63  */

64
65 /*
66  * Copyright 1999, 2000, 2001 Jcorporate Ltd.
67  */

68 package com.jcorporate.expresso.services.controller;
69
70 import com.jcorporate.expresso.core.controller.Controller;
71 import com.jcorporate.expresso.core.controller.ControllerException;
72 import com.jcorporate.expresso.core.controller.ControllerRequest;
73 import com.jcorporate.expresso.core.controller.ControllerResponse;
74 import com.jcorporate.expresso.core.controller.DBController;
75 import com.jcorporate.expresso.core.controller.ErrorCollection;
76 import com.jcorporate.expresso.core.controller.NonHandleableException;
77 import com.jcorporate.expresso.core.controller.Output;
78 import com.jcorporate.expresso.core.controller.ServletControllerRequest;
79 import com.jcorporate.expresso.core.controller.session.PersistentSession;
80 import com.jcorporate.expresso.core.db.DBException;
81 import com.jcorporate.expresso.core.dbobj.ValidValue;
82 import com.jcorporate.expresso.core.misc.ConfigManager;
83 import com.jcorporate.expresso.core.misc.CookieUtil;
84 import com.jcorporate.expresso.core.misc.CurrentLogin;
85 import com.jcorporate.expresso.core.misc.StringUtil;
86 import com.jcorporate.expresso.core.security.DelayThread;
87 import com.jcorporate.expresso.core.security.User;
88 import com.jcorporate.expresso.kernel.util.FastStringBuffer;
89 import org.apache.log4j.Logger;
90
91 import javax.servlet.http.Cookie JavaDoc;
92 import javax.servlet.http.HttpServletRequest JavaDoc;
93 import javax.servlet.http.HttpServletResponse JavaDoc;
94 import java.util.Enumeration JavaDoc;
95 import java.util.Vector JavaDoc;
96
97 /**
98  * Main Login Controller - used for login/logout and basic interaction with
99  * the registration system. This class recognizes the 'registration' classHandler
100  * name in the expresso-config.xml It uses the classname in that field to
101  * construct and forward to the appropriate registration class.
102  * <p/>
103  * Creation date: (5/12/2001 6:36:41 PM)
104  *
105  * @author Shash Chatterjee
106  */

107 public abstract class LoginController
108         extends DBController {
109
110     public static final String JavaDoc LOGINNAME_COOKIE = "UserName";
111     public static final String JavaDoc PASSWORD_COOKIE = "Password";
112     public static final String JavaDoc DBNAME_COOKIE = "db";
113     public static final String JavaDoc CLASS_HANDLER_NAME = "login";
114     public static final String JavaDoc DEFAULT_CLASS_NAME = com.jcorporate.expresso.services.controller.SimpleLoginController.class.getName();
115
116     private static Logger log = Logger.getLogger(LoginController.class);
117
118     /**
119      * LoginController constructor. Sets all the states and parameters
120      * for the system.
121      */

122     public LoginController() {
123         super();
124         //this.setSchema(com.jcorporate.expresso.core.ExpressoSchema.class);
125
}
126
127     /**
128      * Function called to suspend thread execution for x many seconds before
129      * offering a retry to login. Helps to slow down brute force attacks.
130      * [a 40,000 word dictionary attack prolonged by 3 seconds a piece
131      * adds potentially 33 hours to the attack time. Yes this can be partially bypassed
132      * through simultaneous requests, but it still adds significant reponse time]
133      */

134     protected void delayLogin() {
135         DelayThread.delay();
136     }
137
138     /**
139      * Processes the login request. Sets the errors collection if there's
140      * a problem with the login. This method expects the HttpServletRequest to have
141      * two parameters, LoginName and Password
142      *
143      * @param request The ControllerRequest handed off to a controller by the
144      * framework
145      * @param response The ControllerResponse object
146      * @param errors The system fills out the errors collection if there
147      * are problems with the login itself.
148      * @param hreq The "low level" version of ControllerRequest. Allows direct
149      * access to http components.
150      * @param hres The "low level" version of ControllerResponse. Allows direct
151      * access to the http HttpServletResponse.
152      * @param session The PersistantSession object to write the CurrentLogin response to
153      * @return the uid of the user if successfully logged in
154      * @throws ControllerException upon logic error
155      * @throws NonHandleableException upon a fatal error
156      * @throws DBException if there is database lookup problems
157      */

158     protected int attemptLogin(ControllerRequest request,
159                                ControllerResponse response,
160                                ErrorCollection errors,
161                                HttpServletRequest JavaDoc hreq,
162                                HttpServletResponse JavaDoc hres,
163                                PersistentSession session)
164             throws ControllerException, NonHandleableException, DBException {
165         try {
166             int uid = 0;
167             User myUser = new User();
168             myUser.setDataContext(request.getDataContext());
169             String JavaDoc loginName = StringUtil.notNull(request.getParameter("LoginName"));
170
171             if (loginName.equals("")) {
172                 errors.addError("error.nologinname"); // You did not enter a login name, please go back and correct that
173
return uid;
174             }
175
176             myUser.setLoginName(loginName);
177
178             if ("NONE".equalsIgnoreCase(loginName) || !myUser.find()) {
179                 errors.addError("error.invalidusername");
180
181                 // Brute forcing login names is often step one in the brute force attack
182
// So we counter this by giving them only a generic error.
183
// "Login '" + loginName + "' does not exist in context '"
184
// + request.getDBName() + "', please provide another value");
185

186                 //Delay threads provide ways of reducing the effectiveness
187
//of brute force attacks.
188
logInvalidLoginAttempt(response.getString("error.invalidusername"), request);
189                 delayLogin();
190
191                 return uid;
192             }
193             // evaluate the passwords
194
if (!myUser.passwordEquals(StringUtil.notNull(request.getParameter("Password")))) {
195
196                 // Brute forcing login names is often step one in the brute force attack
197
// So we counter this by giving them only a generic error.
198
//errors.addError(errorMsg);
199
errors.addError("error.invalidusername");
200                 //Delay threads provide ways of reducing the effectiveness
201
//of brute force attacks.
202
logInvalidLoginAttempt(response.getString("error.invalidusername"), request);
203                 delayLogin();
204
205                 return uid;
206             }
207
208             uid = myUser.getUid();
209
210             String JavaDoc currStatus = myUser.getAccountStatus();
211
212             // test for account status
213
if (!currStatus.equals(User.ACTIVE_ACCOUNT_STATUS)) {
214                 ValidValue vv = null;
215                 String JavaDoc currDescrip = "Unknown Status '" + currStatus + "'";
216                 Vector JavaDoc v = myUser.getValidValues("AccountStatus");
217
218                 for (Enumeration JavaDoc ev = v.elements(); ev.hasMoreElements();) {
219                     vv = (ValidValue) ev.nextElement();
220
221                     if (vv.getValue().equals(currStatus)) {
222                         currDescrip = vv.getDescription();
223                     }
224                 }
225
226                 this.logInvalidLoginAttempt(response.getString("error.login.invalidlogin", loginName, currDescrip),
227                         request);
228
229                 errors.addError("error.login.invalidlogin", loginName, currDescrip); // Account + loginName + is + currDescrip
230
delayLogin(); //Make 'em wait to save on server resources.
231

232                 return uid;
233             }
234
235             ///////////////////////////////////////////////
236
// GOOD LOGIN If we get here then user is authenticated and active
237
/////////////////////////////////////////////
238

239             // some DBs are case INsensitive, so incorrect-case login names still match,
240
// but we should correct our internal username to the
241
// "real" login name as found in the database
242
try {
243                 User user = new User();
244                 user.setDataContext(request.getDataContext());
245                 user.setUid(uid);
246                 if (!user.find()) {
247                     log.error(
248                             "runDoLoginState unexpectedly cannot find user (after successful login!) for uid: " + uid);
249                 } else {
250                     loginName = user.getLoginName();
251                 }
252             } catch (DBException e) {
253                 log.error("unexpectedly cannot find user" + e);
254             }
255
256             if (log.isInfoEnabled()) {
257                 log.info("Successful login for user: " + loginName);
258             }
259
260
261             // write cookie; write password if user has chosen this option
262
/**
263              * @todo write a internally-maintained, expiring key INSTEAD of password
264              */

265             if (StringUtil.notNull(request.getParameter("Remember")).equalsIgnoreCase("Y")) {
266                 setCookie(loginName, request.getParameter("Password"), hres, false, request.getDataContext());
267                 response.add(new Output("remembered", response.getString("Login_Remembered")));
268             } else { /* if we don't remember, clear the cookie */
269                 setCookie(User.UNKNOWN_USER, "NONE", hres, true, request.getDataContext());
270                 response.add(new Output("remembered",
271                         response.getString("Login_Not_Remembered")));
272             } /* if we remember */
273
274
275             response.setUser(loginName);
276             request.setUser(loginName);
277             request.setUid(uid);
278             // set params
279
setPersistentLoginAttributes(request, loginName);
280
281
282             return uid;
283         } catch (Throwable JavaDoc t) {
284             log.error("Exception caught attempting login", t);
285             throw new ControllerException("Error while attempting login processing", t);
286         }
287     } /* attemptLogin */
288
289     /**
290      * after successful authentication, set all the necessary parameters in session
291      * this method is useful when integrating into different authentication system.
292      * override Controller.perform, and after doing non-expresso authentication in
293      * that override, call this static method,
294      * then proceed with the standard Controller.perform, and expresso will pick
295      * up the login values specified here.
296      * <p/>
297      * this method made static 3/03 in order to allow external authentication;
298      * should be changed to plug-in model when available in v. 5.1 and thereafter
299      *
300      * @param request The ControllerRequest object for this request
301      * @param loginName the login name which has been (potentially) corrected to match case in DB, even if DB matches on any lower/upper case
302      * @throws ControllerException upon error
303      */

304     public static void setPersistentLoginAttributes(ControllerRequest request, String JavaDoc loginName) throws ControllerException {
305         PersistentSession session = request.getSession();
306         ServletControllerRequest sHreq = (ServletControllerRequest) request;
307         HttpServletRequest JavaDoc hreq = sHreq.getHttpServletRequest();
308
309         session.setPersistentAttribute("UserName", loginName);
310         session.setPersistentAttribute("Password", request.getParameter("Password"));
311         session.setPersistentAttribute("db", request.getDataContext());
312         session.removePersistentAttribute(CurrentLogin.LOGIN_KEY);
313
314         CurrentLogin myLogin = CurrentLogin.newInstance(loginName,
315                 hreq.getRemoteAddr(),
316                 request.getDataContext(),
317                 request.getUid());
318
319         session.setPersistentAttribute(CurrentLogin.LOGIN_KEY, myLogin);
320
321     }
322
323
324     /**
325      * Set a long-life cookie on the client side that records the login
326      * information, so the user does not have to log in again
327      * next time they begin a session. CAN BE A SERIOUS SECURITY
328      * RISK - Only used when the PC is physically secured or when the network
329      * login is required and prevents others from using the same browser cookies
330      * <p/>
331      * this method made static 3/03 in order to allow external authentication;
332      * should be changed to plug-in model when available in v. 5.4 and thereafter
333      *
334      * @param userName User name to save in cookie
335      * @param password Password to save in cookie
336      * @param res Standard response object
337      * @param clear Should the cookies be cleared instead?
338      * @param dbname The data context to set the login for.
339      * @throws ControllerException if a database error occurs
340      */

341     public static void setCookie(String JavaDoc userName,
342                                  String JavaDoc password,
343                                  HttpServletResponse JavaDoc res, boolean clear, String JavaDoc dbname)
344             throws ControllerException {
345         try {
346             final int THIRTY_DAYS_IN_SECS = 2592000;
347             Cookie JavaDoc c1;
348
349             if ((userName != null) && (userName.length() > 0)) {
350                 c1 = new Cookie JavaDoc(LOGINNAME_COOKIE, CookieUtil.cookieEncode(userName));
351             } else {
352                 c1 = new Cookie JavaDoc(LOGINNAME_COOKIE, User.UNKNOWN_USER);
353             }
354             if (clear == true) {
355                 c1.setMaxAge(10);
356             } else {
357                 c1.setMaxAge(THIRTY_DAYS_IN_SECS);
358             }
359
360             c1.setPath("/");
361             res.addCookie(c1);
362
363             Cookie JavaDoc c2;
364
365             if ((password != null) && (password.length() > 0)) {
366                 c2 = new Cookie JavaDoc(PASSWORD_COOKIE, CookieUtil.cookieEncode(password));
367             } else {
368                 c2 = new Cookie JavaDoc(PASSWORD_COOKIE, "NONE");
369             }
370             if (clear == true) {
371                 c2.setMaxAge(10); // seconds till expiry
372
} else {
373                 c2.setMaxAge(THIRTY_DAYS_IN_SECS);
374             }
375
376             c2.setPath("/"); //$NON-NLS-1$
377
res.addCookie(c2);
378
379             Cookie JavaDoc c3 = null;
380
381             if (clear == true) {
382                 c3 = new Cookie JavaDoc(DBNAME_COOKIE, "NONE");
383                 c3.setMaxAge(10);
384             } else {
385                 c3 = new Cookie JavaDoc(DBNAME_COOKIE,
386                         CookieUtil.cookieEncode(dbname));
387                 c3.setMaxAge(THIRTY_DAYS_IN_SECS); /* 30 days */
388             }
389
390             c3.setPath("/");
391             res.addCookie(c3);
392         } catch (Exception JavaDoc ce) {
393             throw new ControllerException(ce);
394         }
395     } /* setCookie(String, String, HttpServletRequest, HttpServletResponse, boolean) */
396
397     /**
398      * Prefereable method to call if you already have a controller instance. Use
399      * it instead of getRegistrationController to use the Schema as your source of
400      * login controllers
401      *
402      * @return The Controller Object that is a registration controller
403      * @throws ControllerException if an error occurs instantiating the controller
404      * object
405      */

406
407     public Controller getDefaultRegistrationController() throws ControllerException {
408         return this.getSchemaInstance().getRegistrationController();
409     }
410
411
412     /**
413      * Gets the Login controller based upon the classhandler or the
414      * default....
415      *
416      * @return an instantiated LoginController
417      * @throws ControllerException if there's an error instantiating the LoginController
418      */

419     public static Controller getLoginController()
420             throws ControllerException {
421         String JavaDoc className = ConfigManager.getClassHandler(CLASS_HANDLER_NAME);
422         if (className == null || className.length() == 0) {
423             className = DEFAULT_CLASS_NAME;
424         }
425
426         return ConfigManager.getControllerFactory().getController(className);
427     }
428
429     /**
430      * Prefereable method to call if you already have a controller instance. Use
431      * it instead of getLoginController to use the Schema as your source of
432      * login controllers
433      *
434      * @return The Controller Object that is a registration controller
435      * @throws ControllerException if an error occurs instantiating the controller
436      * object
437      */

438
439     public Controller getDefaultLoginController() throws ControllerException {
440         return this.getSchemaInstance().getLoginController();
441     }
442
443
444     /**
445      * Does a warning log to log the invalid login request. Also logs the
446      * remote IP Address.
447      *
448      * @param msg The main message to log.
449      * @param request If it happens to be a ServletControllerRequest
450      */

451     public void logInvalidLoginAttempt(String JavaDoc msg, ControllerRequest request) {
452         String JavaDoc remoteIP = "";
453         try {
454             remoteIP = ((ServletControllerRequest) request).getServletRequest().getRemoteAddr();
455         } catch (ClassCastException JavaDoc cce) {
456             //We aren't running in a servlet container, we can't get the remote
457
//IP address
458
}
459         FastStringBuffer fsb = new FastStringBuffer(msg.length() + 32);
460         fsb.append(msg);
461         if (remoteIP.length() > 0) {
462             fsb.append("\n Client IP Address: ");
463             fsb.append(remoteIP);
464         }
465
466         log.warn(fsb.toString());
467     }
468
469     /**
470      * Override this class to do some post processing in your derived controllers.
471      *
472      * @param request The ControllerRequest Object
473      * @param response The ControllerResponse Object
474      * @throws ControllerException upon error processing the post login information
475      */

476     public void postLoginProcessing(ControllerRequest request, ControllerResponse response)
477             throws ControllerException {
478         return;
479     }
480
481     /**
482      * Override the normal stateAllowed method to always allow
483      * access to this controller for certain states - otherwise no-one can ever log in :-)
484      *
485      * @param newState the state to transition to.
486      * @param params The controllerRequest object
487      * @return true if the state is allowed for the currently logged in user.
488      * @throws ControllerException if there is an error while looking up the sercurity permissions
489      */

490     public boolean stateAllowed(String JavaDoc newState,
491                                 ControllerRequest params)
492             throws ControllerException {
493         if (newState.equals("promptChangePW") ||
494                 newState.equals("processChangePW") ||
495                 newState.equals("promptLogout")) {
496             return super.stateAllowed(newState, params);
497         }
498
499         return true;
500     } /* stateAllowed(String) */
501
502
503 }
Popular Tags