0001: /* ====================================================================
0002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
0003: *
0004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * 1. Redistributions of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * 2. Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * 3. The end-user documentation included with the redistribution,
0019: * if any, must include the following acknowledgment:
0020: * "This product includes software developed by Jcorporate Ltd.
0021: * (http://www.jcorporate.com/)."
0022: * Alternately, this acknowledgment may appear in the software itself,
0023: * if and wherever such third-party acknowledgments normally appear.
0024: *
0025: * 4. "Jcorporate" and product names such as "Expresso" must
0026: * not be used to endorse or promote products derived from this
0027: * software without prior written permission. For written permission,
0028: * please contact info@jcorporate.com.
0029: *
0030: * 5. Products derived from this software may not be called "Expresso",
0031: * or other Jcorporate product names; nor may "Expresso" or other
0032: * Jcorporate product names appear in their name, without prior
0033: * written permission of Jcorporate Ltd.
0034: *
0035: * 6. No product derived from this software may compete in the same
0036: * market space, i.e. framework, without prior written permission
0037: * of Jcorporate Ltd. For written permission, please contact
0038: * partners@jcorporate.com.
0039: *
0040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
0044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
0046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0051: * SUCH DAMAGE.
0052: * ====================================================================
0053: *
0054: * This software consists of voluntary contributions made by many
0055: * individuals on behalf of the Jcorporate Ltd. Contributions back
0056: * to the project(s) are encouraged when you make modifications.
0057: * Please send them to support@jcorporate.com. For more information
0058: * on Jcorporate Ltd. and its products, please see
0059: * <http://www.jcorporate.com/>.
0060: *
0061: * Portions of this software are based upon other open source
0062: * products and are subject to their respective licenses.
0063: */
0064:
0065: /*
0066: * User.java
0067: *
0068: * Copyright 1999, 2000, 2001 Jcorporate Ltd.
0069: */
0070:
0071: package com.jcorporate.expresso.core.security;
0072:
0073: import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
0074: import com.jcorporate.expresso.core.controller.ControllerRequest;
0075: import com.jcorporate.expresso.core.dataobjects.ContextNested;
0076: import com.jcorporate.expresso.core.dataobjects.DataObject;
0077: import com.jcorporate.expresso.core.dataobjects.Mappable;
0078: import com.jcorporate.expresso.core.db.DBConnection;
0079: import com.jcorporate.expresso.core.db.DBException;
0080: import com.jcorporate.expresso.core.dbobj.LookupInterface;
0081: import com.jcorporate.expresso.core.dbobj.SecuredDBObject;
0082: import com.jcorporate.expresso.core.dbobj.ValidValue;
0083: import com.jcorporate.expresso.core.logging.LogException;
0084: import com.jcorporate.expresso.core.misc.Base64;
0085: import com.jcorporate.expresso.core.misc.ByteArrayDataSource;
0086: import com.jcorporate.expresso.core.misc.ConfigManager;
0087: import com.jcorporate.expresso.core.misc.ConfigurationException;
0088: import com.jcorporate.expresso.core.misc.EMailSender;
0089: import com.jcorporate.expresso.core.misc.EventHandler;
0090: import com.jcorporate.expresso.core.misc.StringUtil;
0091: import com.jcorporate.expresso.kernel.util.FastStringBuffer;
0092: import com.jcorporate.expresso.services.dbobj.UserGroup;
0093: import org.apache.log4j.Logger;
0094:
0095: import java.util.ArrayList;
0096: import java.util.Enumeration;
0097: import java.util.Hashtable;
0098: import java.util.Iterator;
0099: import java.util.List;
0100: import java.util.Vector;
0101:
0102: /**
0103: * This class provides a front-end for maintaining Expresso
0104: * Users. This class provides an abstraction of the minimum
0105: * information required for a valid User in Expresso. This
0106: * class is an "Adaptor" that "adapts" the actual class that
0107: * implements the User object. "Adaptee" user classes must
0108: * implement the UserInfo interface. In addition, classes that
0109: * implement the UserListener interface can listen in on when
0110: * user objects are added, modified or deleted.
0111: *
0112: * @author Michael Nash
0113: * @see com.jcorporate.expresso.core.security.UserInfo interface
0114: * @see com.jcorporate.expresso.core.security.UserListener interface
0115: * @see com.jcorporate.expresso.core.dbobj.LookupInterface
0116: */
0117: public class User implements LookupInterface, Mappable, ContextNested {
0118:
0119: // Name of this class, for log/debug purposes
0120: private static final String this Class = User.class.getName();
0121:
0122: // The actual UserInfo class the implements the "meat" of this User
0123: private UserInfo myUserInfo = null;
0124:
0125: /**
0126: *
0127: */
0128: protected static UserInfo notLoggedInUser = null;
0129:
0130: // The DB context that this User resides in
0131: private String dbName = "default";
0132:
0133: /**
0134: * The log4j logger.
0135: */
0136: private static Logger log = Logger.getLogger(User.class);
0137:
0138: /**
0139: * hash of classes which have registered to listen for CRUD notifications
0140: */
0141: private static ConcurrentReaderHashMap listeners = null;
0142:
0143: /**
0144: * the anonymous user; used in systems where anonymous guest
0145: * are allowed; different than UNKNOWN because there is some kind
0146: * of status/login conferred by system; user id = 1 usually
0147: */
0148: public static final String ANONYMOUS_ALLOWED_GUEST_USER = "Anonymous";
0149:
0150: /**
0151: * the unknown user; no login yet; user id = 2 usually
0152: */
0153: public static final String UNKNOWN_USER = "NONE";
0154:
0155: /**
0156: * the admin user; user id = 3 usually
0157: */
0158: public static final String ADMIN_USER = "Admin";
0159:
0160: /**
0161: * the string code for an active account
0162: *
0163: * @see #setAccountStatus
0164: */
0165: public static final String ACTIVE_ACCOUNT_STATUS = "A";
0166:
0167: /**
0168: * the string code for an disabled account
0169: *
0170: * @see #setAccountStatus
0171: */
0172: public static final String DISABLED_ACCOUNT_STATUS = "D";
0173:
0174: /**
0175: * the string code for an disabled account
0176: *
0177: * @see #setAccountStatus
0178: */
0179: public static final String INACTIVE_ACCOUNT_STATUS = "I";
0180:
0181: /**
0182: * the string code for an account that is awaiting admin approval after registration
0183: *
0184: * @see #setAccountStatus
0185: */
0186: public static final String WAITING_FOR_APPROVAL_ACCOUNT_STATUS = "W";
0187:
0188: /**
0189: * Default constructor
0190: */
0191: public User() {
0192: } /* User() */
0193:
0194: /**
0195: * Constructs a user with a particular user info. Useful for defining
0196: * some special case users where we don't want database access. See
0197: * {@link com.jcorporate.expresso.core.security.SuperUser} for example usage.
0198: *
0199: * @param uInfo UserInfo
0200: */
0201: public User(UserInfo uInfo) {
0202: myUserInfo = uInfo;
0203: }
0204:
0205: /**
0206: * Adds this user to the underlying storage mechanism (SQL-DB, LDAP, etc.)
0207: *
0208: * @throws DBException If the add fails
0209: */
0210: public void add() throws DBException {
0211:
0212: // If a User is added without a status, we set the AccountStatus
0213: // to "I" for security reasons
0214: if ("".equals(getAccountStatus())) {
0215: setAccountStatus("I");
0216: }
0217:
0218: // Let the actual implementation add the user
0219: getUserInfo().add();
0220: //the underlying object must perform the notification upon success
0221: //of the operation.
0222: // addNotify(this.myUserInfo);
0223: } /* add() */
0224:
0225: /**
0226: * Used when a UserInfo Object adds itself outside of the 'User' class.
0227: *
0228: * @param uif a user info object that has been modified.
0229: * @throws DBException if ther's an error performing the 'add' operation
0230: */
0231: public void addNotify(UserInfo uif) throws DBException {
0232: this .myUserInfo = uif;
0233:
0234: // Any classes that implement the UserListener interface
0235: // and then call User.addListener() to register will
0236: // be called back when a new User is added
0237:
0238: if (listeners != null) {
0239: ArrayList theListeners = null;
0240: synchronized (listeners) {
0241: theListeners = new ArrayList(listeners.values());
0242: }
0243:
0244: for (Iterator iterator = theListeners.iterator(); iterator
0245: .hasNext();) {
0246: UserListener listener = (UserListener) iterator.next();
0247: listener.addedUser(this );
0248: }
0249: }
0250: }
0251:
0252: /**
0253: * Adds a UserListener object to list of listeners.
0254: * The listener param is passed in as an instance, but this is a STATIC LISTENER scheme.
0255: * In other words, one instance is not listening to another instance. The *classes*
0256: * are listening to one another. It is generally used as a kind of a poor substitute for 'detail' records,
0257: * which are deleted in a cascading manner. So when the User object is add/modified/deleted, it will
0258: * call the method on the provided instance, which usually causes a search and manipulation of
0259: * all records having to do with the user--not that each record has been registered as listening.
0260: *
0261: * @param listener UserListener The object whose object TYPE wants to listen in on User add/modify/delete; we will keep this instance for listening, but any other object of this type cannot register for listening--it is just a representative instance, and the hash is on class name
0262: * @see com.jcorporate.expresso.services.dbobj.RegistrationDBObject for an example of a listener (it is superclass to several listeners)
0263: */
0264: public static synchronized void addListener(UserListener listener) {
0265: if (listeners == null) {
0266: listeners = new ConcurrentReaderHashMap();
0267: }
0268:
0269: listeners.put(listener.getClass().getName(), listener);
0270: } /* addListener(UserListener) */
0271:
0272: /**
0273: * Determines if this dbobject is already a listener.
0274: *
0275: * @param listener The UserListener class
0276: * @return boolean <b>true</b> if the class is already registered as a listener
0277: */
0278: public static synchronized boolean isListener(UserListener listener) {
0279: if (listeners == null) {
0280: return false;
0281: }
0282:
0283: return listeners.get(listener.getClass().getName()) != null;
0284: }
0285:
0286: /**
0287: * This method just checks to make sure that the submitted emailAuthCode
0288: * matches the real code.
0289: * Creation date: (8/8/00 3:08:49 PM)
0290: * author: Adam Rossi, PlatinumSolutions, Inc.
0291: *
0292: * @param emailAuthCode java.lang.String
0293: * @return boolean
0294: * @throws com.jcorporate.expresso.core.db.DBException
0295: * The exception description.
0296: */
0297: public boolean checkEmailAuthCode(String emailAuthCode)
0298: throws DBException {
0299: try {
0300: String realEmailAuthCode = "";
0301: realEmailAuthCode = getEmailAuthCode();
0302:
0303: if (realEmailAuthCode == null) {
0304: realEmailAuthCode = "";
0305: }
0306: if (realEmailAuthCode.equals(emailAuthCode)) {
0307: return true;
0308: } else {
0309: return false;
0310: }
0311: } catch (Exception e) {
0312: throw new DBException("Error in checking Email Auth Code: "
0313: + e.toString());
0314: }
0315: } /* checkEmailAuthCode(String) */
0316:
0317: /**
0318: * Uncaches the underlying UserInfo implementation object
0319: *
0320: * @throws com.jcorporate.expresso.core.db.DBException
0321: * The exception description.
0322: */
0323: public synchronized void clear() throws DBException {
0324: myUserInfo = null;
0325: } /* clear() */
0326:
0327: /**
0328: * Calls back registered listeners so they can cleanup
0329: * and then deletes the user
0330: *
0331: * @throws DBException If the delete fails
0332: */
0333: public void delete() throws DBException {
0334:
0335: // deleteNotify(this.myUserInfo);
0336: //the underlying object must perform the notification upon success
0337: //of the operation.
0338:
0339: // Delete the user
0340: getUserInfo().delete();
0341: } /* delete() */
0342:
0343: /**
0344: * Used for when a UserInfo implementation deletes itself outside of the
0345: * User Interface
0346: *
0347: * @param uif The user info that has been modified.
0348: */
0349: public void deleteNotify(UserInfo uif) throws DBException {
0350: //
0351: //This must be kept since it is possible that it was
0352: //getting modified outside the User object
0353: //
0354: //
0355: this .myUserInfo = uif;
0356:
0357: // Any classes that implement the UserListener interface
0358: // and then call User.addListener() to register will
0359: // be called back right before a User is deleted
0360:
0361: if (listeners != null) {
0362: ArrayList theListeners = null;
0363: synchronized (listeners) {
0364: theListeners = new ArrayList(listeners.values());
0365: }
0366:
0367: for (Iterator iterator = theListeners.iterator(); iterator
0368: .hasNext();) {
0369: UserListener listener = (UserListener) iterator.next();
0370: listener.deletedUser(this );
0371: }
0372: }
0373: }
0374:
0375: /**
0376: * Find a user matching the values of the properties that have been set
0377: *
0378: * @return true if user is found
0379: * @throws DBException Thrown if the underlying implementation throws an exception
0380: */
0381: public boolean find() throws DBException {
0382: return getUserInfo().find();
0383: } /* find() */
0384:
0385: /**
0386: * Returns the current status of the account. Possible states are
0387: * active, disabled, inactive.
0388: *
0389: * @return java.lang.String
0390: * @throws DBException If the underlying User implementation throws the same
0391: */
0392: public String getAccountStatus() throws DBException {
0393: return getUserInfo().getAccountStatus();
0394: } /* getAccountStatus(String) */
0395:
0396: /**
0397: * Returns a list of all the Users in the database. The returned Vector contains
0398: * UserInfo objects.
0399: *
0400: * @return java.util.Vector
0401: * @throws DBException If the underlying User implementation throws the same
0402: */
0403: public Vector getAllUsers() throws DBException {
0404: return getUserInfo().getAllUsers();
0405: } /* getAllUsers() */
0406:
0407: /**
0408: * Returns the currently set DB context
0409: *
0410: * @return java.util.String
0411: */
0412: public String getDataContext() {
0413: return dbName;
0414: } /* getDBName() */
0415:
0416: /**
0417: * Returns the email address of the user
0418: *
0419: * @return java.lang.String
0420: * @throws com.jcorporate.expresso.core.db.DBException
0421: * If the underlying User implementation throws the same
0422: */
0423: public String getEmail() throws DBException {
0424: return getUserInfo().getEmail();
0425: } /* getEmail() */
0426:
0427: /**
0428: * Here we generate an authorization code that would be hard for someone to
0429: * guess. The idea is that the person has to check the email sent to them to
0430: * get this number, and then click on the specially encoded URL to ensure
0431: * that he/she actually is checking the email account used at registration.
0432: * <p/>
0433: * The little trick of getting the time in milliseconds that the person
0434: * registered, multiplying by some constant, and then rounding, is extremely
0435: * weak. We need a better method of generating a unique code that will
0436: * "play nice" in a query string.
0437: * <p/>
0438: * Creation date: (8/8/00 3:00:41 PM)
0439: * author: Adam Rossi, PlatinumSolutions, Inc.
0440: *
0441: * @return java.lang.String
0442: */
0443: public String getEmailAuthCode() throws DBException {
0444: return getUserInfo().getEmailAuthCode();
0445: } /* getEmailAuthCode() */
0446:
0447: /**
0448: * Returns the code required for authorization via email
0449: *
0450: * @return java.lang.String
0451: * @throws DBException If the underlying User implementation throws the same
0452: */
0453: public String getEmailValCode() throws DBException {
0454: return getUserInfo().getEmailValCode();
0455: } /* getEmailValCode() */
0456:
0457: /**
0458: * Return a vector of the group names that this user belongs to
0459: *
0460: * @return Vector Group names that this user belongs to
0461: * @throws DBException If an error occurs when the group info is read
0462: */
0463: public Vector getGroups() throws DBException {
0464: UserInfo myInfo = getUserInfo();
0465: return myInfo.getGroups();
0466: } /* getGroups() */
0467:
0468: /**
0469: * Return a List of the group names that this user belongs to
0470: *
0471: * @return Vector Group names that this user belongs to
0472: * @throws DBException If an error occurs when the group info is read
0473: * @todo Modify userInfo so that getGroupsList() is implemented so we
0474: * can deprecate getGroups();
0475: */
0476: public List getGroupsList() throws DBException {
0477: UserInfo myInfo = getUserInfo();
0478:
0479: return new ArrayList(myInfo.getGroups());
0480: }
0481:
0482: /**
0483: * Returns the string that the user needs to use to login.
0484: *
0485: * @return java.lang.String
0486: * @throws DBException If the underlying User implementation throws the same
0487: */
0488: public String getLoginName() throws DBException {
0489: return getUserInfo().getLoginName();
0490: } /* getLoginName() */
0491:
0492: /**
0493: * Returns the password string for the user. NOTE: The user class itself has no idea whether the
0494: * password was encoded or not, that will be upto the implementation of whatever
0495: * servlet/controller that sets the password in the first place. Conversely, the servlet/controller
0496: * that logs the user in must know whether the password that this method retrieves was encoded or
0497: * not.
0498: *
0499: * @return java.lang.String
0500: * @throws DBException If the underlying User implementation throws the same
0501: */
0502: public String getPassword() throws DBException {
0503: return getUserInfo().getPassword();
0504: } /* getPassword() */
0505:
0506: /**
0507: * Returns the static constant notLogged in user.
0508: *
0509: * @return An instantiated UserInfo object
0510: */
0511: public synchronized UserInfo getNotLoggedInUser()
0512: throws DBException {
0513: if (notLoggedInUser == null) {
0514: notLoggedInUser = constructNotLoggedInUser();
0515: }
0516:
0517: return notLoggedInUser;
0518: }
0519:
0520: /**
0521: * @return the 'not logged in user'
0522: */
0523: protected UserInfo constructNotLoggedInUser() throws DBException {
0524: UserInfo ui = this .getUserInfo();
0525: ui.setLoginName(UNKNOWN_USER);
0526: ui.find();
0527:
0528: return ui;
0529: }
0530:
0531: /**
0532: * Whether registration has been completed beyond the basic user info
0533: *
0534: * @return java.lang.String
0535: * @throws DBException If the underlying UserInfo implementation throws the same
0536: */
0537: public boolean getRegComplete() throws DBException {
0538: return getUserInfo().getRegComplete();
0539: } /* getRegComplete()*/
0540:
0541: /**
0542: * Returns the unique integer for the registration domain that this user belongs to
0543: *
0544: * @return java.lang.String
0545: * @throws com.jcorporate.expresso.core.db.DBException
0546: * If the underlying User implementation throws the same
0547: */
0548: public String getRegistrationDomain() throws DBException {
0549: return getUserInfo().getRegistrationDomain();
0550: } /* getRegistrationDomainId() */
0551:
0552: /**
0553: * Returns the unique integer used as a key to identify the user
0554: *
0555: * @return java.lang.String
0556: * @throws com.jcorporate.expresso.core.db.DBException
0557: * If the underlying User implementation throws the same
0558: */
0559: public String getUidString() throws DBException {
0560: return Integer.toString(getUserInfo().getUid());
0561: } /* getUidString() */
0562:
0563: public int getUid() throws DBException {
0564: return getUserInfo().getUid();
0565: }
0566:
0567: /**
0568: * Return the UserInfo object Normally you would not use this directly, except
0569: * in special cases where you need to get to the UserInfo object.
0570: *
0571: * @return the UserInfo object
0572: * @throws DBException If an error occurs when setting dbname in a newly created UserInfo object
0573: */
0574: public UserInfo getUserInfo() throws DBException {
0575:
0576: if (myUserInfo != null) {
0577: return myUserInfo;
0578: }
0579:
0580: String infoClass = StringUtil.notNull(ConfigManager
0581: .getClassHandler("userInfo"));
0582:
0583: if (infoClass.equals("")) {
0584: infoClass = com.jcorporate.expresso.services.dbobj.DefaultUserInfo.class
0585: .getName();
0586: }
0587: try {
0588: Object o = Class.forName(infoClass).newInstance();
0589:
0590: if (!(o instanceof UserInfo)) {
0591: throw new DBException(this Class + "getUserInfo()"
0592: + ":The class " + infoClass
0593: + " is not a UserInfo object - it's a "
0594: + o.getClass().getName());
0595: }
0596:
0597: myUserInfo = (UserInfo) o;
0598: } catch (ClassNotFoundException ce) {
0599: throw new DBException(this Class + "getUserInfo()"
0600: + ":Class not found '" + infoClass, ce);
0601: } catch (InstantiationException ie) {
0602: throw new DBException(this Class + "getUserInfo()"
0603: + ":Error instantiating class '" + infoClass, ie);
0604: } catch (IllegalAccessException iae) {
0605: throw new DBException(this Class + "getUserInfo()"
0606: + ":Illegal access error "
0607: + "instantiating class '" + infoClass, iae);
0608: }
0609:
0610: myUserInfo.setDBName(this .getDataContext());
0611:
0612: return myUserInfo;
0613: } /* getUserInfo() */
0614:
0615: /**
0616: * Returns a descriptiptive, longer name for the user
0617: * this is NOT THE UNIQUE LOGIN NAME.
0618: *
0619: * @return java.lang.String
0620: * @throws DBException If the underlying User implementation throws the same
0621: * @see #getLoginName()
0622: */
0623: public String getDisplayName() throws DBException {
0624: return getUserInfo().getUserName();
0625: } /* getUserName() */
0626:
0627: /**
0628: * Returns the possible values for the multivalued properties
0629: *
0630: * @param fieldName The fieldname to get the valid values for
0631: * @return java.util.Vector
0632: * @throws DBException The exception description.
0633: */
0634: public synchronized Vector getValidValues(String fieldName)
0635: throws DBException {
0636:
0637: if ("AccountStatus".equals(fieldName)) {
0638: Vector values = new Vector();
0639: values.addElement(new ValidValue("A", "Active"));
0640: values.addElement(new ValidValue("I",
0641: "Inactive Until Email Confirmation"));
0642: values.addElement(new ValidValue("D", "Disabled"));
0643: values.addElement(new ValidValue("W",
0644: "Waiting For Approval"));
0645: values.addElement(new ValidValue("X",
0646: "Registration Denied By Administrator"));
0647:
0648: return values;
0649: }
0650:
0651: return null;
0652: } /* getValidValues(String) */
0653:
0654: public Vector getValues()
0655: throws com.jcorporate.expresso.core.db.DBException {
0656: return getUserInfo().getValues();
0657: }
0658:
0659: /**
0660: * hashEncodePassword: If passed a plaintext string > 0 bytes, then this
0661: * function will hash the password, then Base64 encode it and return it
0662: * as a string.
0663: * <p/>
0664: * If the password is zero length, then it will simply return a zero length
0665: * string also.
0666: *
0667: * @param plaintext - the password to process in this manner: <br>
0668: * returnValue = Base64(SHA-1(plaintext))
0669: * @return a String representing the password hashencoded
0670: */
0671: private String hashEncodePassword(String plaintext)
0672: throws DBException {
0673: if (plaintext == null) {
0674: throw new DBException(this Class + "hashEncodePassword()"
0675: + ": Password Must not be NULL");
0676: }
0677: if (plaintext.length() == 0) {
0678: return plaintext;
0679: }
0680: try {
0681: return Base64.encode(CryptoManager.getInstance()
0682: .getStringHash().produceHash(plaintext.getBytes()));
0683: } catch (Exception ex) {
0684: throw new DBException(this Class + "hashEncodePassword()"
0685: + ":Error hashing Password:"
0686: + " You may not have installed the"
0687: + " Cryptography Extensions Properly:", ex);
0688: }
0689: } /* hashEncodePassword(String) */
0690:
0691: /**
0692: * Send this user a notification via e-mail.
0693: *
0694: * @param subject Subject of the e-mail
0695: * @param message Message to send in body of e-mail
0696: * @throws DBException If the mail message cannot be sent
0697: */
0698: public void notify(String subject, String message)
0699: throws DBException {
0700: notify(subject, message, false);
0701: } /* notify(String, String) */
0702:
0703: /**
0704: * Notify the user with the optional parameter of using html format
0705: *
0706: * @param subject the message subject
0707: * @param message the message, possibly in html format
0708: * @param htmlFormat true if you want the message to be html formatted.
0709: * @throws DBException upon error
0710: */
0711: public void notify(String subject, String message,
0712: boolean htmlFormat) throws DBException {
0713: notify(subject, message, htmlFormat, null);
0714: }
0715:
0716: /**
0717: * Notify the user with the optional parameter of using html
0718: * format with virtual raw data attachments.
0719: *
0720: * @param subject the message subject
0721: * @param message the message, possibly in html format
0722: * @param htmlFormat true if you want the message to be html formatted.
0723: * @param attachments an primitive array of raw data byte array data sources.
0724: * @throws DBException upon error from database
0725: * @see com.jcorporate.expresso.core.misc.ByteArrayDataSource
0726: */
0727: public void notify(String subject, String message,
0728: boolean htmlFormat, ByteArrayDataSource attachments[])
0729: throws DBException {
0730:
0731: if (log.isDebugEnabled()) {
0732: log.debug("Notifying user " + getUserInfo().getLoginName()
0733: + " of " + subject);
0734: }
0735:
0736: String sendToUser = getUserInfo().getEmail();
0737:
0738: try {
0739: EMailSender ems = new EMailSender();
0740: ems.setDBName(this .getDataContext());
0741: if (htmlFormat) {
0742: ems.setEmailHtmlFormat();
0743: }
0744:
0745: if (attachments != null) {
0746: // Register virtual raw data attachments
0747: for (int k = 0; k < attachments.length; ++k) {
0748: ems.addDataSourceAttachment(attachments[k]);
0749: }
0750: }
0751:
0752: ems.send(sendToUser, subject, message);
0753: } catch (Exception e) {
0754: throw new DBException(this Class + "notify()"
0755: + ":Uncaught exception sending e-mail", e);
0756: }
0757: } /* notify(String, String) */
0758:
0759: /**
0760: * Check if the given number is in the range of letters and
0761: * digits that we want to use for generating a password
0762: * Previously in com.jcorporate.expresso.ext.servlet.RegisterUser.java
0763: *
0764: * @param x The random number to check against
0765: * @return true if the paramter is a printable character
0766: */
0767: private boolean okNumber(double x) {
0768: int oneNumber = new Double(x).intValue();
0769:
0770: if ((oneNumber >= 65) && (oneNumber <= 90)) {
0771: return true;
0772: }
0773: if ((oneNumber >= 48) && (oneNumber <= 57)) {
0774: return true;
0775: }
0776: if ((oneNumber >= 97) && (oneNumber <= 122)) {
0777: return true;
0778: }
0779:
0780: return false;
0781: } /* okNumber(double) */
0782:
0783: /**
0784: * passwordEquals - feed it a password and it will tell you if the hash of it
0785: * matches the one on file.
0786: *
0787: * @param tryPassword The value the user input for an attempted login.
0788: * @return boolean
0789: */
0790: public boolean passwordEquals(String tryPassword)
0791: throws DBException {
0792: if (tryPassword == null) {
0793: throw new DBException(this Class
0794: + ":tryPassword Must not be NULL");
0795: }
0796:
0797: return getUserInfo().passwordEquals(tryPassword);
0798: } /* passwordEquals(String) */
0799:
0800: /**
0801: * Called by the various objects that can log in a user
0802: * to do post-login tasks
0803: */
0804: public void postLogin() throws DBException, LogException {
0805: UserInfo myInfo = getUserInfo();
0806: UserGroup oneGroup = new UserGroup(
0807: SecuredDBObject.SYSTEM_ACCOUNT);
0808: oneGroup.setDataContext(getDataContext());
0809:
0810: String theEvent = null;
0811: Hashtable allEvents = new Hashtable(1);
0812: String oneGroupName = null;
0813:
0814: for (Enumeration gl = getGroups().elements(); gl
0815: .hasMoreElements();) {
0816: oneGroupName = (String) gl.nextElement();
0817: oneGroup.clear();
0818: oneGroup.setField("GroupName", oneGroupName);
0819:
0820: if (oneGroup.find()) {
0821: theEvent = oneGroup.getField("LoginEvent");
0822:
0823: if (!theEvent.equals("")) {
0824: allEvents.put(theEvent, oneGroup
0825: .getField("GroupName"));
0826: }
0827: } /* if the group exists */
0828:
0829: } /* for each group user is a member of */
0830:
0831: /* if any events need to be triggered... */
0832: String theMessage = null;
0833:
0834: if (allEvents.size() > 0) {
0835: for (Enumeration el = allEvents.keys(); el
0836: .hasMoreElements();) {
0837: theEvent = (String) el.nextElement();
0838: theMessage = "User " + myInfo.getLoginName() + " ("
0839: + myInfo.getUserName() + ") who is a member "
0840: + " of group "
0841: + (String) allEvents.get(theEvent)
0842: + " has just logged in.";
0843: EventHandler.Event(getDataContext(), theEvent,
0844: theMessage, true);
0845: } /* for each event */
0846:
0847: } /* if any events to be triggered */
0848:
0849: } /* postLogin() */
0850:
0851: /**
0852: * Generates a random plaintext password
0853: *
0854: * @return java.lang.String
0855: */
0856: public String randomPassword() {
0857: int passwordLength;
0858: double oneNumber = 0;
0859: char oneChar;
0860: int iterations = 0;
0861:
0862: //
0863: //Read the property value of minimum password length
0864: //
0865: String propValue = "";
0866:
0867: try {
0868: StringUtil.notNull(ConfigManager.getContext(
0869: getDataContext()).getMinPasswordSize());
0870: } catch (ConfigurationException ce) {
0871: propValue = "";
0872: }
0873: if (!propValue.equals("")) {
0874: try {
0875: passwordLength = Integer.parseInt(propValue, 10);
0876: } catch (NumberFormatException ex) {
0877:
0878: //Bad number
0879: passwordLength = 6;
0880: }
0881: } else {
0882: passwordLength = 6;
0883: }
0884:
0885: FastStringBuffer newPassword = new FastStringBuffer(
0886: passwordLength);
0887: /////////////////////////////////
0888: //
0889: //Now Generate the new password. (Code from servlet.RegisterUser) before)
0890: //
0891: while ((newPassword.length() < passwordLength)
0892: && (iterations < 200)) {
0893: iterations++;
0894: oneNumber = Math.random() * 100;
0895:
0896: if (okNumber(oneNumber)) {
0897: oneChar = (char) new Double(oneNumber).intValue();
0898: newPassword.append(oneChar);
0899: }
0900: }
0901:
0902: return newPassword.toString();
0903: } /* randomPassword() */
0904:
0905: /**
0906: * @throws com.jcorporate.expresso.core.db.DBException
0907: * The exception description.
0908: */
0909: public void retrieve() throws DBException {
0910: getUserInfo().retrieve();
0911: } /* retrieve() */
0912:
0913: /**
0914: * @throws DBException
0915: */
0916: public void sendAuthEmail() throws DBException {
0917: getUserInfo().sendAuthEmail();
0918: } /* sendAuthEmail() */
0919:
0920: /**
0921: * Send this user an e-mail with file attachments.
0922: *
0923: * @param subject Subject of the e-mail
0924: * @param message Message to send in body of e-mail
0925: * @param fileNames of the files to attach
0926: * @throws DBException If the mail message cannot be sent
0927: */
0928: public void sendFileTo(String subject, String message,
0929: Vector fileNames) throws DBException, LogException {
0930: UserInfo myInfo = getUserInfo();
0931: log.debug("Sending " + fileNames.size()
0932: + " files via e-mail to " + myInfo.getLoginName());
0933:
0934: String sendToUser = getUserInfo().getEmail();
0935:
0936: try { // create some properties and get the default Session
0937: EMailSender ems = new EMailSender();
0938: ems.setDBName(getDataContext());
0939: ems.addFileAttachments(fileNames);
0940: ems.send(sendToUser, subject, message);
0941: } catch (Exception e) {
0942: throw new DBException(this Class + "sendFileTo"
0943: + ":Error sending e-mail", e);
0944: }
0945: } /* sendFileTo(String, String, Vector) */
0946:
0947: /**
0948: * @throws DBException
0949: */
0950: public void sendFollowUpEmail() throws DBException {
0951: getUserInfo().sendFollowUpEmail();
0952: } /* sendFollowUpEmail() */
0953:
0954: /**
0955: * Sets the current status of the account
0956: *
0957: * @param accountStatus java.lang.String One of the values "A" (active), "I" (inactive), "D" (disabled)
0958: * @throws DBException If the underlying UserInfo implementation throws the same
0959: */
0960: public void setAccountStatus(String accountStatus)
0961: throws DBException {
0962: getUserInfo().setAccountStatus(accountStatus);
0963: } /* setAccountStatus(String) */
0964:
0965: /**
0966: * Switches data contexts for the User Object
0967: *
0968: * @param newDBName The new data context to use this security object for
0969: * @throws com.jcorporate.expresso.core.db.DBException
0970: * The exception description.
0971: */
0972: public synchronized void setDBName(String newDBName)
0973: throws DBException {
0974: dbName = newDBName;
0975: getUserInfo().setDBName(newDBName);
0976: } /* setDBName(String) */
0977:
0978: /**
0979: * Sets the email address for the user
0980: *
0981: * @param email the new email address of this user
0982: * @throws com.jcorporate.expresso.core.db.DBException
0983: * If the underlying User implementation throws the same
0984: */
0985: public void setEmail(String email) throws DBException {
0986: getUserInfo().setEmail(email);
0987: } /* setEmail(String) */
0988:
0989: /**
0990: * Sets the validation code for authorization via email
0991: *
0992: * @param code the new validation code
0993: */
0994: public void setEmailValCode(String code) throws DBException {
0995: getUserInfo().setEmailValCode(code);
0996: } /* setEmailValCode(String) */
0997:
0998: /**
0999: * Sets the name to be used by this user to login
1000: *
1001: * @param loginName the new login name for this user object
1002: * @throws com.jcorporate.expresso.core.db.DBException
1003: * If the underlying User implementation throws the same
1004: */
1005: public void setLoginName(String loginName) throws DBException {
1006: getUserInfo().setLoginName(loginName);
1007: } /* setLoginName(String) */
1008:
1009: /**
1010: * Sets the password to be used by the user. The password is expected to be plaintext
1011: *
1012: * @param password the new password to set for this user
1013: * @throws com.jcorporate.expresso.core.db.DBException
1014: * If the underlying User implementation throws the same
1015: */
1016: public void setPassword(String password) throws DBException {
1017: // getUserInfo().setPassword(hashEncodePassword(password));
1018: getUserInfo().setPassword(
1019: getUserInfo().hashEncodePassword(password));
1020: } /* setPassword(String) */
1021:
1022: /**
1023: * Sets the status of whether the extended registration has been completed or not
1024: *
1025: * @param status java.lang.String Valid values are "Y" or "N"
1026: * @throws DBException If the underlying UserInfo implementation throws the same
1027: */
1028: public void setRegComplete(boolean status) throws DBException {
1029: getUserInfo().setRegComplete(status);
1030: } /* setRegComplete(boolean) */
1031:
1032: /**
1033: * Sets the registration domain that this user belongs to
1034: *
1035: * @param domain The registration domain that this user belongs to.
1036: * @throws com.jcorporate.expresso.core.db.DBException
1037: * If the underlying User implementation throws the same
1038: */
1039: public void setRegistrationDomain(String domain) throws DBException {
1040: getUserInfo().setRegistrationDomain(domain);
1041: } /* setRegistrationDomainId() */
1042:
1043: /**
1044: * Sets the uid, used for finding...
1045: *
1046: * @param uid The new UID integer to set this user object to.
1047: * @throws com.jcorporate.expresso.core.db.DBException
1048: * If the underlying User implementation throws the same
1049: */
1050: public void setUid(int uid) throws DBException {
1051: getUserInfo().setUid(uid);
1052: } /* setUid(String) */
1053:
1054: /**
1055: * Sets the user id. Parsing the string to an integer
1056: *
1057: * @param uid a string containing an integer value
1058: * @throws DBException upon parse error
1059: */
1060: public void setUid(String uid) throws DBException {
1061: try {
1062: int uidAsInt = new Integer(uid).intValue();
1063: getUserInfo().setUid(uidAsInt);
1064: } catch (NumberFormatException ne) {
1065: throw new DBException(ne);
1066: }
1067: }
1068:
1069: /**
1070: * Sets a long, descriptive name for the user
1071: *
1072: * @param name the new 'full name' of the user
1073: * @throws com.jcorporate.expresso.core.db.DBException
1074: * If the underlying User implementation throws the same
1075: */
1076: public void setDisplayName(String name) throws DBException {
1077: getUserInfo().setUserName(name);
1078: } /* setUserName(String) */
1079:
1080: /**
1081: * This method is called to update the properties of an User object.
1082: * All changed and unchanged fields must have their values filled in
1083: * The best way is to do a find() on the User first, and then to set
1084: * the modified fields before calling this method.
1085: *
1086: * @throws com.jcorporate.expresso.core.db.DBException
1087: * The exception description.
1088: */
1089: public void update() throws DBException {
1090:
1091: // Update the user's info
1092: getUserInfo().update();
1093:
1094: //the underlying object must perform the notification upon success
1095: //of the operation.
1096: // updateNotify(this.myUserInfo);
1097: } /* update() */
1098:
1099: /**
1100: * Used to notify UserListeners that a UserInfo object has modified
1101: * itself outside of the User object.
1102: *
1103: * @param uif The new user info to update
1104: */
1105: public void updateNotify(UserInfo uif) throws DBException {
1106: //
1107: //This must be kept since it is possible that it was
1108: //getting modified outside the User object
1109: //
1110: //
1111: this .myUserInfo = uif;
1112:
1113: // Any classes that implement the UserListener interface
1114: // and then call User.addListener() to register will
1115: // be called back right before a User is deleted
1116: if (listeners != null) {
1117: ArrayList theListeners = null;
1118: synchronized (listeners) {
1119: theListeners = new ArrayList(listeners.values());
1120: }
1121:
1122: for (Iterator iterator = theListeners.iterator(); iterator
1123: .hasNext();) {
1124: UserListener listener = (UserListener) iterator.next();
1125: listener.modifiedUser(this );
1126: }
1127: }
1128: }
1129:
1130: /**
1131: * is set first time that isAdmin() is run
1132: */
1133: private static Integer ADMIN_ID = null;
1134:
1135: /**
1136: * determine if this user is admin
1137: *
1138: * @return true if this user is the Administrator account
1139: */
1140: public boolean isAdmin() throws DBException {
1141: if (ADMIN_ID == null) {
1142: int id = getIdFromLogin(ADMIN_USER, getDataContext());
1143: ADMIN_ID = new Integer(id);
1144: }
1145:
1146: return ADMIN_ID.intValue() == this .getUid();
1147: }
1148:
1149: /**
1150: * determine if this user is admin
1151: * WARNING: assumes dbname = 'default'
1152: *
1153: * @param id of user
1154: * @return true if the id is an Admin user.
1155: * @throws DBException upon error
1156: */
1157: public static boolean isAdmin(int id) throws DBException {
1158: User user = new User();
1159: user.setUid(id);
1160: return user.isAdmin();
1161: }
1162:
1163: /**
1164: * determine admin id (usually '3')
1165: * WARNING: assumes dbname = 'default'
1166: *
1167: * @return the uid of user "Admin"
1168: * @throws DBException upon error
1169: */
1170: public static int getAdminId() throws DBException {
1171: return getAdminId(DBConnection.DEFAULT_DB_CONTEXT_NAME);
1172: }
1173:
1174: /**
1175: * Retrieves the User instance of the current user.
1176: *
1177: * @param dbName String the data context to check.
1178: * @return User a User instance.
1179: * @throws DBException if there was no admin user found or
1180: * other database errors.
1181: */
1182: public static User getAdmin(String dbName) throws DBException {
1183: User user = new User();
1184: user.setDataContext(dbName);
1185: user.setLoginName(ADMIN_USER);
1186: if (!user.find()) {
1187: throw new DBException("Unable to locate: " + ADMIN_USER);
1188: }
1189:
1190: return user;
1191: }
1192:
1193: /**
1194: * determine admin id (usually '3')
1195: *
1196: * @param dbname the Database context to use
1197: * @return the id of the admin
1198: * @throws DBException upon error
1199: */
1200: public static int getAdminId(String dbname) throws DBException {
1201: User user = new User();
1202: user.setDataContext(dbname);
1203: user.isAdmin(); // make sure first test is done
1204: return ADMIN_ID.intValue();
1205: }
1206:
1207: /**
1208: * determine if this user is admin
1209: *
1210: * @param name the login name to test
1211: * @return true if the name is the Admin user.
1212: */
1213: public static boolean isAdmin(String name) {
1214: return ADMIN_USER.equals(name);
1215: }
1216:
1217: /**
1218: * determine if this user is unknown
1219: *
1220: * @param name the login name of the user
1221: * @return true if the name is UNKNOWN_USER ("NONE")
1222: */
1223: public static boolean isUnknownUser(String name) {
1224: return UNKNOWN_USER.equals(name);
1225: }
1226:
1227: /**
1228: * the primary group of this user is appropriate for unix-like purposes,
1229: * such as setting the group for a file permission
1230: *
1231: * @return name of the primary group of this user; null if no group is primary
1232: * @throws DBException upon database access error
1233: */
1234: public String getPrimaryGroup() throws DBException {
1235: return getUserInfo().getPrimaryGroup();
1236: }
1237:
1238: /**
1239: * utility
1240: *
1241: * @param login the login name
1242: * @param myDBname the DataContext to use.
1243: * @return the integer uid of the given login
1244: * @throws DBException upon databaes access error or if the uid cannot be found.
1245: */
1246: public static int getIdFromLogin(String login, String myDBname)
1247: throws DBException {
1248: User user = new User();
1249: user.setDataContext(myDBname);
1250: user.setLoginName(login);
1251: if (!user.find()) {
1252: throw new DBException("cannot find user: " + login);
1253: }
1254:
1255: return user.getUid();
1256: }
1257:
1258: /**
1259: * Pull up the User object associated with the integer id
1260: *
1261: * @param uid the integer user id
1262: * @param dataContext data context to look in.
1263: * @return a built User object
1264: * @throws DBException if the user is unable to be found.
1265: */
1266: public static User getUserFromId(int uid, String dataContext)
1267: throws DBException {
1268: User user = new User();
1269: user.setDataContext(dataContext);
1270: user.setUid(uid);
1271: if (!user.find()) {
1272: throw new DBException("cannot find user: " + uid);
1273: }
1274: return user;
1275: }
1276:
1277: /**
1278: * utility
1279: *
1280: * @param uid the uid of the user
1281: * @param dataContext the data context to use
1282: * @return the Login for the given UID
1283: * @throws DBException upon database access error or if the given id cannot be found.
1284: */
1285: public static String getLoginFromId(int uid, String dataContext)
1286: throws DBException {
1287: User user = User.getUserFromId(uid, dataContext);
1288: return user.getLoginName();
1289: }
1290:
1291: /**
1292: * Given a 'friendly name' get the key value for this object. Example, mappedValue
1293: * = "Admin", then getKeyValue("Admin") will return "3"
1294: *
1295: * @param mappedValue the value to convert to a key field. It will return null
1296: * if the mapped value does not exist.
1297: * @return java.lang.String or null.
1298: */
1299: public String getKeyValue(String mappedValue) {
1300: try {
1301: User u = new User();
1302: u.setDataContext(this .getDataContext());
1303: u.setLoginName(mappedValue);
1304: if (u.find()) {
1305: return u.getUidString();
1306: } else {
1307: return null;
1308: }
1309: } catch (DBException ex) {
1310: log.error("Error getting key value", ex);
1311: return null;
1312: }
1313: }
1314:
1315: /**
1316: * Given a key value, return the mapped value. So for example:
1317: * getMappedValue("3") will return "Admin"
1318: *
1319: * @param keyValue the key value to map
1320: * @return the mapped value. It will return null if the key does not
1321: * exist
1322: */
1323: public String getMappedValue(String keyValue) {
1324: try {
1325: User u = new User();
1326: u.setDataContext(this .getDataContext());
1327: u.setUid(keyValue);
1328: if (u.find()) {
1329: return u.getLoginName();
1330: } else {
1331: return null;
1332: }
1333: } catch (DBException ex) {
1334: log.error("Error getting key value", ex);
1335: return null;
1336: }
1337:
1338: }
1339:
1340: /**
1341: * It returns if the field description for the mapped value as specified by the
1342: * key value parameter.
1343: * <p>Total Hack warning: This function assumes you're running a DataObject
1344: * with the login name field called: LoginName
1345: *
1346: * @return String the mapped description
1347: */
1348: public String getMappedDescription() {
1349: try {
1350: return ((DataObject) this .getUserInfo()).getFieldMetaData(
1351: "LoginName").getDescription();
1352: } catch (DBException ex) {
1353: log.error("Error getting description", ex);
1354: return "Login Name";
1355: } catch (ClassCastException cce) {
1356: return "Login Name";
1357: }
1358: }
1359:
1360: /**
1361: * Sets the data context for this particular object implementation.
1362: *
1363: * @param newContext the new Data context.
1364: * @throws IllegalArgumentException if the data context is null.
1365: */
1366: public void setDataContext(String newContext) {
1367: try {
1368: this .setDBName(newContext);
1369: } catch (DBException ex) {
1370: log.error("Error setting data context", ex);
1371: throw new IllegalArgumentException(ex.getMessage());
1372: }
1373: }
1374:
1375: /**
1376: * Retrieve the data context for this particular object implementation
1377: *
1378: * @return java.lang.String. Should never be null.
1379: */
1380: public String getDBName() {
1381: return this .getDataContext();
1382: }
1383:
1384: /**
1385: * Convenience routine to find current user.
1386: *
1387: * @param request current request
1388: * @return current user
1389: * @throws DBException upon database access error
1390: */
1391: public static User getUser(ControllerRequest request)
1392: throws DBException {
1393: return getUserFromId(request.getUid(), request.getDataContext());
1394: }
1395:
1396: /**
1397: * determine whether this user is in this group
1398: *
1399: * @param candidategroupname the name to check against.
1400: * @return true if the provided group name is one of the groups that this user is a member of
1401: * @throws DBException upon database access error
1402: */
1403: public boolean isMember(String candidategroupname)
1404: throws DBException {
1405: boolean result = false;
1406: List list = getGroupsList();
1407: for (Iterator iterator = list.iterator(); iterator.hasNext();) {
1408: String grpname = (String) iterator.next();
1409: if (grpname.equals(candidategroupname)) {
1410: result = true;
1411: break;
1412: }
1413: }
1414: return result;
1415: }
1416: } /* User */
|