001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: /**
066: * Registration.java
067: *
068: * Copyright 2000, 2001 Jcorporate Ltd.
069: */package com.jcorporate.expresso.services.controller;
070:
071: import com.jcorporate.expresso.core.controller.ControllerException;
072: import com.jcorporate.expresso.core.controller.ControllerRequest;
073: import com.jcorporate.expresso.core.controller.ControllerResponse;
074: import com.jcorporate.expresso.core.controller.ServletControllerRequest;
075: import com.jcorporate.expresso.core.db.DBException;
076: import com.jcorporate.expresso.core.dbobj.DBObject;
077: import com.jcorporate.expresso.core.dbobj.SecuredDBObject;
078: import com.jcorporate.expresso.core.misc.ConfigManager;
079: import com.jcorporate.expresso.core.misc.StringUtil;
080: import com.jcorporate.expresso.core.security.DelayThread;
081: import com.jcorporate.expresso.core.security.User;
082: import com.jcorporate.expresso.services.dbobj.RegistrationDomain;
083: import com.jcorporate.expresso.services.dbobj.RegistrationObjectMap;
084: import com.jcorporate.expresso.services.validation.AuthValidationException;
085: import com.jcorporate.expresso.services.validation.ValidationEntry;
086: import org.apache.log4j.Logger;
087:
088: import javax.servlet.http.HttpServletRequest;
089: import java.text.NumberFormat;
090: import java.util.ArrayList;
091: import java.util.Iterator;
092:
093: /**
094: * <p>Registration Controller. Provides services for self-registering people... ie
095: * sign-up pages on websites. This is the abstract class from which all Registration class
096: * should extend from. This class contains most of the non prompt/process methods</p>
097: * <p/>
098: * Depends on:<br />
099: * <ul>
100: * <li>Login Controller</li>
101: * <li>Email Validator</li>
102: * <li>Registration Validator</li>
103: * </ul>
104: */
105:
106: public abstract class Registration extends
107: com.jcorporate.expresso.core.controller.DBController {
108:
109: private static Logger log = Logger.getLogger(Registration.class
110: .getName());
111:
112: public Registration() {
113: super ();
114: this
115: .setSchema(com.jcorporate.expresso.core.ExpressoSchema.class);
116: }
117:
118: /**
119: * Given the request object, get the logged in user associated
120: * with this request.
121: *
122: * @param request The ControllerRequest object associated with this request
123: * @return A built User object for this session.
124: */
125: protected User getRegUser(ControllerRequest request)
126: throws ControllerException {
127:
128: String loginName = request.getUser();
129:
130: if (loginName.equals("")) {
131: throw new ControllerException("You are not logged-in");
132: }
133:
134: User user = null;
135:
136: try {
137: user = new User();
138: user.setDataContext(request.getDataContext());
139: user.setLoginName(loginName);
140:
141: if (!user.find()) {
142: throw new ControllerException("Account \"" + loginName
143: + "\" not found");
144: }
145:
146: if (user.getAccountStatus().equals("D")) {
147: throw new ControllerException("Account \"" + loginName
148: + "\" has been disabled");
149: } else if (user.getAccountStatus().equals("I")) {
150: throw new ControllerException("Account \"" + loginName
151: + "\" has not been activated yet");
152: }
153: } catch (DBException dbe) {
154: throw new ControllerException(
155: "Database access error for login \"" + loginName
156: + "\"", dbe);
157: }
158:
159: return user;
160: }
161:
162: /**
163: * Check a specific UID to see if their registration is complete.
164: * If it is, update the user record to reflect this
165: *
166: * @param request The ControllerRequest object
167: * @param uid The UID
168: * @return true if our records show that the user's registration is complete
169: */
170: protected boolean checkRegComplete(ControllerRequest request,
171: int uid) throws DBException, ControllerException {
172: User myUser = new User();
173: myUser.setDataContext(request.getDataContext());
174: myUser.setUid(uid);
175: myUser.retrieve();
176:
177: int regDomId = 1;
178: RegistrationDomain dom = new RegistrationDomain();
179: dom.setDataContext(request.getDataContext());
180: dom.setField("Name", myUser.getRegistrationDomain());
181:
182: if (dom.find()) {
183: regDomId = dom.getFieldInt("RegDomId");
184: }
185:
186: RegistrationObjectMap rm = new RegistrationObjectMap();
187: rm.setDataContext(request.getDataContext());
188: rm.setField("RegDomId", regDomId);
189:
190: boolean regComplete = true;
191: RegistrationObjectMap oneMap = null;
192:
193: for (Iterator i = rm.searchAndRetrieveList().iterator(); i
194: .hasNext();) {
195: oneMap = (RegistrationObjectMap) i.next();
196:
197: int min = oneMap.getFieldInt("RecMin");
198: SecuredDBObject oneObj = loadDBObject(request, oneMap
199: .getField("RegObj"));
200: oneObj.setField(oneMap.getField("UidField"), uid);
201:
202: if (oneObj.count() < min) {
203: regComplete = false;
204: break;
205: }
206: }
207:
208: myUser.setRegComplete(regComplete);
209: myUser.update();
210:
211: return regComplete;
212: }
213:
214: /**
215: * Function called to suspend thread execution for x many seconds before
216: * offering a retry to login. Helps to slow down brute force attacks.
217: * [a 40,000 word dictionary attack prolonged by 3 seconds a piece
218: * adds potentially 33 hours to the attack time. Yes this can be partially bypassed
219: * through simultaneous requests, but it still adds significant reponse time]
220: */
221: protected void delayLogin() {
222: DelayThread.delay();
223: }
224:
225: /**
226: * Format a value for display in the HTML being returned to the client
227: *
228: * @param fieldType The type of the field to format
229: * @param fieldValue The value of the field
230: * @return String The formatted field
231: * @throws ControllerException If the field format information could not be
232: * determined
233: */
234: protected String displayValue(String fieldType, String fieldValue)
235: throws ControllerException {
236: try {
237: if (fieldType.equalsIgnoreCase("money")) {
238: if (!fieldValue.equals("")) {
239: return NumberFormat.getCurrencyInstance().format(
240: new Double(fieldValue).doubleValue());
241: }
242: } else {
243: return fieldValue;
244: }
245: } catch (NumberFormatException ne) {
246: throw new ControllerException("Number for field not in a "
247: + "valid numeric format:" + fieldValue, ne);
248: }
249:
250: return null;
251: } /* displayValue(String, String) */
252:
253: /**
254: * Returns the db listed based upon the dbobj parameter.
255: *
256: * @param request The ControllerRequest object
257: * @return the data context name that we're supposed to register with
258: */
259: public String getDB(ControllerRequest request)
260: throws ControllerException {
261: String dbobj = StringUtil
262: .notNull(request.getParameter("dbobj"));
263:
264: if (dbobj.equals("")) {
265: throw new ControllerException(
266: "dbobj parameter was not specified");
267: }
268:
269: return dbobj;
270: }
271:
272: /**
273: * ?????
274: *
275: * @param fieldName ????
276: * @return currently null
277: */
278: protected String getDefaultValue(String fieldName) {
279: return null;
280: }
281:
282: /**
283: * Based upon the user and the ControllerRequest, return the registration
284: * domain belonging to this session
285: *
286: * @param request The ControllerRequest of this request.
287: * @param user the user associated with this session.
288: * @return a built registration domain object
289: */
290: protected RegistrationDomain getRegDomain(
291: ControllerRequest request, User user)
292: throws ControllerException {
293: RegistrationDomain rd = null;
294:
295: try {
296: String domain = user.getRegistrationDomain();
297: rd = new RegistrationDomain();
298: rd.setDataContext(request.getDataContext());
299: rd.setField("Name", domain);
300:
301: if (!rd.find()) {
302: throw new ControllerException("Domain " + domain
303: + " not created yet");
304: }
305: } catch (DBException dbe) {
306: throw new ControllerException("Database error", dbe);
307: }
308:
309: return rd;
310: }
311:
312: /**
313: * Instantiated a dbobject based upon the request and dbobject requested.
314: *
315: * @param request the ControllerRequest Object
316: * @param dbobj The name of the dbobject to instantiate
317: * @return a fully instantiated SecuredDbObject
318: */
319: protected SecuredDBObject loadDBObject(ControllerRequest request,
320: String dbobj) throws ControllerException {
321: SecuredDBObject db = null;
322:
323: try {
324: db = (SecuredDBObject) Class.forName(dbobj).newInstance();
325: db.setRequestingUid(SecuredDBObject.SYSTEM_ACCOUNT);
326: db.setDataContext(request.getDataContext());
327: db.setLocale(request.getLocale());
328: } catch (Exception e) {
329: throw new ControllerException(
330: "Instantiate failed for database object " + dbobj,
331: e);
332: }
333:
334: return db;
335: }
336:
337: /**
338: * Return the name of the next record required for a complete
339: * registration, or null of no more are required.
340: *
341: * @param request the ControllerRequest Object
342: * @return java.lang.String that is the name of the next dbobject required
343: * to be filled out for registration
344: */
345: public String nextToAdd(ControllerRequest request)
346: throws ControllerException {
347: if (log.isDebugEnabled()) {
348: log.debug("Checking if user '" + request.getUser()
349: + "' is fully registered, db '"
350: + request.getDataContext() + "'");
351: }
352: try {
353: User user = new User();
354: user.setDataContext(request.getDataContext());
355: user.setUid(request.getUid());
356: user.retrieve();
357: if (log.isDebugEnabled()) {
358: log.debug("Checking registration for used id '"
359: + user.getUid() + "'");
360: }
361:
362: RegistrationDomain rd = new RegistrationDomain();
363: rd.setDataContext(request.getDataContext());
364: rd.setField("Name", user.getRegistrationDomain());
365:
366: if (!rd.find()) {
367: throw new ControllerException("Domain "
368: + user.getRegistrationDomain()
369: + " not created yet");
370: }
371:
372: RegistrationObjectMap rom = new RegistrationObjectMap();
373: rom.setDataContext(request.getDataContext());
374: rom.setField("RegDomId", rd.getField("RegDomId"));
375:
376: RegistrationObjectMap oneRom = null;
377:
378: for (Iterator e = rom.searchAndRetrieveList("RegOrder")
379: .iterator(); e.hasNext();) {
380: oneRom = (RegistrationObjectMap) e.next();
381:
382: DBObject db = isRegistrationObjectNeeded(request, user,
383: oneRom);
384:
385: if (db != null) {
386: return db.getClass().getName();
387: }
388: }
389: } catch (DBException de) {
390: throw new ControllerException(de);
391: }
392:
393: return null;
394: }
395:
396: /**
397: * Loads the dbobject and returns it if it is required.
398: *
399: * @param request The framework's controller Request object
400: * @param user The instantiated user for the registration.
401: * @param rom The RegistrationObjectMap to count.
402: * @return boolean if the record count is less than the minimum needed.
403: */
404: protected DBObject isRegistrationObjectNeeded(
405: ControllerRequest request, User user,
406: RegistrationObjectMap rom) throws DBException {
407:
408: int min;
409: int records = 0;
410:
411: DBObject returnObject = null;
412: rom.setDataContext(request.getDataContext());
413: min = rom.getFieldInt("RecMin");
414:
415: if (log.isDebugEnabled()) {
416: log.debug("Need at least " + min + " records for '"
417: + rom.getField("RegObj") + "'");
418: }
419:
420: try {
421: returnObject = (DBObject) Class.forName(
422: rom.getField("RegObj")).newInstance();
423: } catch (Exception ex) {
424: log
425: .error(
426: "isRegistrationObjectNeeded() Dynanamic load failed.",
427: ex);
428: throw new DBException("Dynamic load failed for "
429: + rom.getField("RegObj"), ex);
430: }
431:
432: returnObject.setDataContext(request.getDataContext());
433:
434: if (user != null) {
435: String fieldName = rom.getField("UidField");
436: returnObject.setField(fieldName, user.getUid());
437: if (log.isDebugEnabled()) {
438: log.debug("Looking for '"
439: + returnObject.getClass().getName()
440: + "' record with " + fieldName + " = "
441: + user.getUid() + " in db "
442: + request.getDataContext());
443: }
444:
445: records = returnObject.count();
446: if (log.isDebugEnabled()) {
447: log.debug("Found " + records);
448: }
449: }
450:
451: if (records < min) {
452: if (log.isDebugEnabled()) {
453: log.debug("There were '" + records + "' records, but "
454: + min + " are required for '"
455: + returnObject.getClass().getName() + "'");
456: }
457:
458: return returnObject;
459:
460: } else {
461: if (log.isDebugEnabled()) {
462: log.debug("There were '" + records + "' records for "
463: + returnObject.getClass().getName()
464: + ", so we don't need any more.");
465: }
466:
467: return null;
468: }
469: }
470:
471: /**
472: * Returns the required registration dbobjects... ie, what's the minimum
473: * to do?
474: *
475: * @param request The System ControllerRequest
476: * @param rd The currently set up RegistrationDomain for this user
477: * @param user (optional) if null means that the user is new and all normally
478: * required dbobjects will be returned.
479: * @return a DBObject array of required dbobjects to be filled out
480: * @throws DBException if there's an error mapping the registration domains
481: * and building the DBObjects.
482: */
483: protected DBObject[] getRequiredDBObjects(
484: ControllerRequest request, RegistrationDomain rd, User user)
485: throws DBException {
486:
487: ArrayList dbobjects = new ArrayList();
488:
489: if (log.isDebugEnabled()) {
490: log.debug("Getting the registration objects that user '"
491: + request.getUser() + "' needs, db='"
492: + request.getDataContext() + "'");
493: }
494:
495: //
496: // User user = new User();
497: // user.setDBName(request.getDBName());
498: // user.setUid(request.getParameter("uid"));
499: // user.retrieve();
500: // if (log.isDebugEnabled()) {
501: // log.debug("Checking registration for used id '" + user.getUid() +
502: // "'");
503: // }
504:
505: // RegistrationDomain rd = new RegistrationDomain();
506: // rd.setDBName(request.getDBName());
507: // rd.setField("Name", user.getRegistrationDomain());
508:
509: if (!rd.find()) {
510: throw new DBException("Domain "
511: + user.getRegistrationDomain() + " not created yet");
512: }
513:
514: RegistrationObjectMap rom = new RegistrationObjectMap(
515: SecuredDBObject.SYSTEM_ACCOUNT);
516: rom.setDataContext(request.getDataContext());
517: rom.setField("RegDomId", rd.getField("RegDomId"));
518:
519: RegistrationObjectMap oneRom = null;
520:
521: for (Iterator e = rom.searchAndRetrieveList("RegOrder")
522: .iterator(); e.hasNext();) {
523: oneRom = (RegistrationObjectMap) e.next();
524:
525: DBObject db = isRegistrationObjectNeeded(request, user,
526: oneRom);
527:
528: if (db != null) {
529: if (user != null) {
530: db.setField(oneRom.getField("UidField"), user
531: .getUid());
532: }
533: dbobjects.add(db);
534:
535: }
536: }
537:
538: return (DBObject[]) dbobjects.toArray(new DBObject[dbobjects
539: .size()]);
540: }
541:
542: /**
543: * Given the ControllerRequest get what the login controller is. If the request
544: * doesn't have the loginController parameter, then it uses the default Expresso
545: * login controller instead.
546: *
547: * @param request The ControllerRequest that may or may not have the loginController
548: * parameter.
549: * @return The classname of the login controller to use.
550: */
551: protected String getLoginController(ControllerRequest request) {
552: final String defaultController = com.jcorporate.expresso.services.controller.LoginController.class
553: .getName();
554: boolean defaultValue = false;
555: String controller = request.getParameter("login");
556: if (controller == null) {
557: defaultValue = true;
558: try {
559: controller = this .getSchemaInstance()
560: .getLoginController().getClass().getName();
561: } catch (ControllerException ex) {
562: controller = defaultController;
563: }
564: }
565:
566: //
567: //Check if the requested login controller is actually instantiatable.
568: //
569: if (!defaultValue) {
570: try {
571: ConfigManager.getControllerFactory().getController(
572: controller);
573: } catch (Exception e) {
574: log.error("Unable to instantiate login controller: "
575: + controller
576: + " . Using default login controller instead",
577: e);
578:
579: controller = defaultController;
580: }
581: }
582:
583: return controller;
584: }
585:
586: /**
587: * Creates the validation entry for validating email.
588: *
589: * @param request The ControllerRequest object
590: * @param response The ControllerResponse object
591: * @param user the user that is registering
592: * @param rd The registration domain that the user is signing up for
593: * @param loginControllerName the name of the login controller used
594: * @throws DBException upon data access error
595: * @throws ControllerException for other errors
596: */
597: protected void setupEmailValidation(ControllerRequest request,
598: ControllerResponse response, User user,
599: RegistrationDomain rd, String loginControllerName)
600: throws DBException, ControllerException {
601:
602: String emailAuthCode = user.getEmailAuthCode();
603: String loginName = user.getLoginName();
604: user.setEmailValCode(emailAuthCode);
605: user.update();
606:
607: HttpServletRequest hreq = (HttpServletRequest) ((ServletControllerRequest) request)
608: .getServletRequest();
609:
610: try {
611:
612: ValidationEntry ve = new ValidationEntry(request
613: .getDataContext());
614: ve.expiresAfter(72, 0, 0); //Expires after 72 hours
615: ve
616: .setValidationHandler(com.jcorporate.expresso.services.validation.LoginEmailValidator.class
617: .getName());
618: ve.setTitle("Registration Email Validation");
619: ve.setDesc("user=" + loginName + ", db="
620: + request.getDataContext());
621: ve.setServer(hreq.getServerName());
622: ve.setPort(Integer.toString(hreq.getServerPort()));
623: ve.setContextPath(hreq.getContextPath());
624: ve.addParam("db", request.getDataContext());
625: ve.addParam("UserName", loginName);
626: ve.addParam("RegistrationController", this .getClass()
627: .getName());
628: ve.addParam("LoginController", loginControllerName);
629: ve.submit();
630: } catch (AuthValidationException avex) {
631: delayLogin();
632: throw new ControllerException(
633: "Validation framework exception", avex);
634: }
635: }
636:
637: }
|