    001    package org.apache.turbine.services.security.torque;
    002
    003    /*
    004     * Licensed to the Apache Software Foundation (ASF) under one
    005     * or more contributor license agreements.  See the NOTICE file
    006     * distributed with this work for additional information
    007     * regarding copyright ownership.  The ASF licenses this file
    008     * to you under the Apache License, Version 2.0 (the
    009     * "License"); you may not use this file except in compliance
    010     * with the License.  You may obtain a copy of the License at
    011     *
    012     *   http://www.apache.org/licenses/LICENSE-2.0
    013     *
    014     * Unless required by applicable law or agreed to in writing,
    015     * software distributed under the License is distributed on an
    016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    017     * KIND, either express or implied.  See the License for the
    018     * specific language governing permissions and limitations
    019     * under the License.
    020     */
    021
    022    import java.util.Iterator;
    023    import java.util.List;
    024
    025    import org.apache.commons.configuration.Configuration;
    026    import org.apache.commons.lang.StringUtils;
    027    import org.apache.torque.om.Persistent;
    028    import org.apache.torque.util.Criteria;
    029    import org.apache.turbine.om.security.User;
    030    import org.apache.turbine.services.InitializationException;
    031    import org.apache.turbine.services.security.TurbineSecurity;
    032    import org.apache.turbine.services.security.UserManager;
    033    import org.apache.turbine.util.security.DataBackendException;
    034    import org.apache.turbine.util.security.EntityExistsException;
    035    import org.apache.turbine.util.security.PasswordMismatchException;
    036    import org.apache.turbine.util.security.UnknownEntityException;
    037
    038    /**
    039     * An UserManager performs {@link org.apache.turbine.om.security.User}
    040     * objects related tasks on behalf of the
    041     * {@link org.apache.turbine.services.security.BaseSecurityService}.
    042     *
    043     * This implementation uses a relational database for storing user data. It
    044     * expects that the User interface implementation will be castable to
    045     * {@link org.apache.torque.om.BaseObject}.
    046     *
    047     * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
    048     * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
    049     * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
    050     * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
    051     * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
    052     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    053     * @version $Id: TorqueUserManager.java 1096130 2011-04-23 10:37:19Z ludwig $
    054     */
    055    public class TorqueUserManager
    056        implements UserManager
    057    {
    058        /**
    059         * Initializes the UserManager
    060         *
    061         * @param conf A Configuration object to init this Manager
    062         *
    063         * @throws InitializationException When something went wrong.
    064         */
    065        public void init(Configuration conf)
    066            throws InitializationException
    067        {
    068            UserPeerManager.init(conf);
    069        }
    070
    071        /**
    072         * Check whether a specified user's account exists.
    073         *
    074         * The login name is used for looking up the account.
    075         *
    076         * @param user The user to be checked.
    077         * @return true if the specified account exists
    078         * @throws DataBackendException if there was an error accessing
    079         *         the data backend.
    080         */
    081        public boolean accountExists(User user)
    082            throws DataBackendException
    083        {
    084            return accountExists(user.getName());
    085        }
    086
    087        /**
    088         * Check whether a specified user's account exists.
    089         *
    090         * The login name is used for looking up the account.
    091         *
    092         * @param userName The name of the user to be checked.
    093         * @return true if the specified account exists
    094         * @throws DataBackendException if there was an error accessing
    095         *         the data backend.
    096         */
    097        public boolean accountExists(String userName)
    098            throws DataBackendException
    099        {
    100            Criteria criteria = new Criteria();
    101            criteria.add(UserPeerManager.getNameColumn(), userName);
    102            List users;
    103            try
    104            {
    105                users = UserPeerManager.doSelect(criteria);
    106            }
    107            catch (Exception e)
    108            {
    109                throw new DataBackendException(
    110                    "Failed to check account's presence", e);
    111            }
    112            if (users.size() > 1)
    113            {
    114                throw new DataBackendException(
    115                    "Multiple Users with same username '" + userName + "'");
    116            }
    117            return (users.size() == 1);
    118        }
    119
    120        /**
    121         * Retrieve a user from persistent storage using username as the
    122         * key.
    123         *
    124         * @param userName the name of the user.
    125         * @return an User object.
    126         * @exception UnknownEntityException if the user's account does not
    127         *            exist in the database.
    128         * @exception DataBackendException if there is a problem accessing the
    129         *            storage.
    130         */
    131        public User retrieve(String userName)
    132            throws UnknownEntityException, DataBackendException
    133        {
    134            Criteria criteria = new Criteria();
    135            criteria.add(UserPeerManager.getNameColumn(), userName);
    136
    137            List users = retrieveList(criteria);
    138
    139            if (users.size() > 1)
    140            {
    141                throw new DataBackendException(
    142                    "Multiple Users with same username '" + userName + "'");
    143            }
    144            if (users.size() == 1)
    145            {
    146                return (User) users.get(0);
    147            }
    148            throw new UnknownEntityException("Unknown user '" + userName + "'");
    149        }
    150
    151        /**
    152         * Retrieve a user from persistent storage using the primary key
    153         *
    154         * @param key The primary key object
    155         * @return an User object.
    156         * @throws UnknownEntityException if the user's record does not
    157         *         exist in the database.
    158         * @throws DataBackendException if there is a problem accessing the
    159         *         storage.
    160         */
    161        public User retrieveById(Object key)
    162                throws UnknownEntityException, DataBackendException
    163        {
    164            Criteria criteria = new Criteria();
    165            criteria.add(UserPeerManager.getIdColumn(), key);
    166
    167            List users = retrieveList(criteria);
    168
    169            if (users.size() > 1)
    170            {
    171                throw new DataBackendException(
    172                    "Multiple Users with same unique Key '" + String.valueOf(key) + "'");
    173            }
    174            if (users.size() == 1)
    175            {
    176                return (User) users.get(0);
    177            }
    178            throw new UnknownEntityException("Unknown user with key '" + String.valueOf(key) + "'");
    179        }
    180
    181        /**
    182         * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
    183         *
    184         * @param criteria The criteria of selection.
    185         * @return a List of users meeting the criteria.
    186         * @throws DataBackendException if there is a problem accessing the
    187         *         storage.
    188         */
    189        public User[] retrieve(Object criteria)
    190            throws DataBackendException
    191        {
    192            return (User [])retrieveList(criteria).toArray(new User[0]);
    193        }
    194
    195        /**
    196         * Retrieve a list of users that meet the specified criteria.
    197         *
    198         * As the keys for the criteria, you should use the constants that
    199         * are defined in {@link User} interface, plus the names
    200         * of the custom attributes you added to your user representation
    201         * in the data storage. Use verbatim names of the attributes -
    202         * without table name prefix in case of Torque implementation.
    203         *
    204         * @param criteria The criteria of selection.
    205         * @return a List of users meeting the criteria.
    206         * @throws DataBackendException if there is a problem accessing the
    207         *         storage.
    208         */
    209        public List retrieveList(Object criteria)
    210            throws DataBackendException
    211        {
    212            if (criteria instanceof Criteria)
    213            {
    214                Criteria c = (Criteria)criteria;
    215                for (Iterator keys = c.keySet().iterator(); keys.hasNext(); )
    216                {
    217                    String key = (String) keys.next();
    218
    219                    // set the table name for all attached criterion
    220                    Criteria.Criterion[] criterion =
    221                        c.getCriterion(key).getAttachedCriterion();
    222
    223                    for (int i = 0; i < criterion.length; i++)
    224                    {
    225                        if (StringUtils.isEmpty(criterion[i].getTable()))
    226                        {
    227                            criterion[i].setTable(UserPeerManager.getTableName());
    228                        }
    229                    }
    230                }
    231                List users = null;
    232                try
    233                {
    234                    users = UserPeerManager.doSelect(c);
    235                }
    236                catch (Exception e)
    237                {
    238                    throw new DataBackendException("Failed to retrieve users", e);
    239                }
    240                return users;
    241            }
    242            else
    243            {
    244                throw new DataBackendException("Failed to retrieve users with invalid criteria");
    245            }
    246        }
    247
    248        /**
    249         * Retrieve a user from persistent storage using username as the
    250         * key, and authenticate the user. The implementation may chose
    251         * to authenticate to the server as the user whose data is being
    252         * retrieved.
    253         *
    254         * @param userName the name of the user.
    255         * @param password the user supplied password.
    256         * @return an User object.
    257         * @exception PasswordMismatchException if the supplied password was
    258         *            incorrect.
    259         * @exception UnknownEntityException if the user's account does not
    260         *            exist in the database.
    261         * @exception DataBackendException if there is a problem accessing the
    262         *            storage.
    263         */
    264        public User retrieve(String userName, String password)
    265            throws PasswordMismatchException, UnknownEntityException,
    266                   DataBackendException
    267        {
    268            User user = retrieve(userName);
    269            authenticate(user, password);
    270            return user;
    271        }
    272
    273        /**
    274         * Save an User object to persistent storage. User's account is
    275         * required to exist in the storage.
    276         *
    277         * @param user an User object to store.
    278         * @exception UnknownEntityException if the user's account does not
    279         *            exist in the database.
    280         * @exception DataBackendException if there is a problem accessing the
    281         *            storage.
    282         */
    283        public void store(User user)
    284            throws UnknownEntityException, DataBackendException
    285        {
    286            if (!accountExists(user))
    287            {
    288                throw new UnknownEntityException("The account '" +
    289                                                 user.getName() + "' does not exist");
    290            }
    291
    292            try
    293            {
    294                // this is to mimic the old behavior of the method, the user
    295                // should be new that is passed to this method.  It would be
    296                // better if this was checked, but the original code did not
    297                // care about the user's state, so we set it to be appropriate
    298                ((Persistent) user).setNew(false);
    299                ((Persistent) user).setModified(true);
    300                ((Persistent) user).save();
    301            }
    302            catch (Exception e)
    303            {
    304                throw new DataBackendException("Failed to save user object", e);
    305            }
    306        }
    307
    308        /**
    309         * Saves User data when the session is unbound. The user account is required
    310         * to exist in the storage.
    311         *
    312         * LastLogin, AccessCounter, persistent pull tools, and any data stored
    313         * in the permData hashtable that is not mapped to a column will be saved.
    314         *
    315         * @exception UnknownEntityException if the user's account does not
    316         *            exist in the database.
    317         * @exception DataBackendException if there is a problem accessing the
    318         *            storage.
    319         */
    320        public void saveOnSessionUnbind(User user)
    321            throws UnknownEntityException, DataBackendException
    322        {
    323            if (!user.hasLoggedIn())
    324            {
    325                return;
    326            }
    327            store(user);
    328        }
    329
    330
    331        /**
    332         * Authenticate an User with the specified password. If authentication
    333         * is successful the method returns nothing. If there are any problems,
    334         * exception was thrown.
    335         *
    336         * @param user an User object to authenticate.
    337         * @param password the user supplied password.
    338         * @exception PasswordMismatchException if the supplied password was
    339         *            incorrect.
    340         * @exception UnknownEntityException if the user's account does not
    341         *            exist in the database.
    342         * @exception DataBackendException if there is a problem accessing the
    343         *            storage.
    344         */
    345        public void authenticate(User user, String password)
    346            throws PasswordMismatchException, UnknownEntityException,
    347                   DataBackendException
    348        {
    349            if (!accountExists(user))
    350            {
    351                throw new UnknownEntityException("The account '" +
    352                                                 user.getName() + "' does not exist");
    353            }
    354
    355            // log.debug("Supplied Pass: " + password);
    356            // log.debug("User Pass: " + user.getPassword());
    357
    358            /*
    359             * Unix crypt needs the existing, encrypted password text as
    360             * salt for checking the supplied password. So we supply it
    361             * into the checkPassword routine
    362             */
    363
    364            if (!TurbineSecurity.checkPassword(password, user.getPassword()))
    365            {
    366                throw new PasswordMismatchException("The passwords do not match");
    367            }
    368        }
    369
    370        /**
    371         * Change the password for an User. The user must have supplied the
    372         * old password to allow the change.
    373         *
    374         * @param user an User to change password for.
    375         * @param oldPassword The old password to verify
    376         * @param newPassword The new password to set
    377         * @exception PasswordMismatchException if the supplied password was
    378         *            incorrect.
    379         * @exception UnknownEntityException if the user's account does not
    380         *            exist in the database.
    381         * @exception DataBackendException if there is a problem accessing the
    382         *            storage.
    383         */
    384        public void changePassword(User user, String oldPassword,
    385                                   String newPassword)
    386            throws PasswordMismatchException, UnknownEntityException,
    387                   DataBackendException
    388        {
    389            if (!accountExists(user))
    390            {
    391                throw new UnknownEntityException("The account '" +
    392                                                 user.getName() + "' does not exist");
    393            }
    394
    395            if (!TurbineSecurity.checkPassword(oldPassword, user.getPassword()))
    396            {
    397                throw new PasswordMismatchException(
    398                    "The supplied old password for '" + user.getName() +
    399                    "' was incorrect");
    400            }
    401            user.setPassword(TurbineSecurity.encryptPassword(newPassword));
    402            // save the changes in the database imediately, to prevent the password
    403            // being 'reverted' to the old value if the user data is lost somehow
    404            // before it is saved at session's expiry.
    405            store(user);
    406        }
    407
    408        /**
    409         * Forcibly sets new password for an User.
    410         *
    411         * This is supposed by the administrator to change the forgotten or
    412         * compromised passwords. Certain implementatations of this feature
    413         * would require administrative level access to the authenticating
    414         * server / program.
    415         *
    416         * @param user an User to change password for.
    417         * @param password the new password.
    418         * @exception UnknownEntityException if the user's record does not
    419         *            exist in the database.
    420         * @exception DataBackendException if there is a problem accessing the
    421         *            storage.
    422         */
    423        public void forcePassword(User user, String password)
    424            throws UnknownEntityException, DataBackendException
    425        {
    426            if (!accountExists(user))
    427            {
    428                throw new UnknownEntityException("The account '" +
    429                                                 user.getName() + "' does not exist");
    430            }
    431            user.setPassword(TurbineSecurity.encryptPassword(password));
    432            // save the changes in the database immediately, to prevent the
    433            // password being 'reverted' to the old value if the user data
    434            // is lost somehow before it is saved at session's expiry.
    435            store(user);
    436        }
    437
    438        /**
    439         * Creates new user account with specified attributes.
    440         *
    441         * @param user The object describing account to be created.
    442         * @param initialPassword the password for the new account
    443         * @throws DataBackendException if there was an error accessing
    444         the data backend.
    445         * @throws EntityExistsException if the user account already exists.
    446         */
    447        public void createAccount(User user, String initialPassword)
    448            throws EntityExistsException, DataBackendException
    449        {
    450            if(StringUtils.isEmpty(user.getName()))
    451            {
    452                throw new DataBackendException("Could not create "
    453                                               + "an user with empty name!");
    454            }
    455
    456            if (accountExists(user))
    457            {
    458                throw new EntityExistsException("The account '" +
    459                                                user.getName() + "' already exists");
    460            }
    461            user.setPassword(TurbineSecurity.encryptPassword(initialPassword));
    462
    463            try
    464            {
    465                // this is to mimic the old behavior of the method, the user
    466                // should be new that is passed to this method.  It would be
    467                // better if this was checked, but the original code did not
    468                // care about the user's state, so we set it to be appropriate
    469                ((Persistent) user).setNew(true);
    470                ((Persistent) user).setModified(true);
    471                ((Persistent) user).save();
    472            }
    473            catch (Exception e)
    474            {
    475                throw new DataBackendException("Failed to create account '" +
    476                                               user.getName() + "'", e);
    477            }
    478        }
    479
    480        /**
    481         * Removes an user account from the system.
    482         *
    483         * @param user the object describing the account to be removed.
    484         * @throws DataBackendException if there was an error accessing
    485         the data backend.
    486         * @throws UnknownEntityException if the user account is not present.
    487         */
    488        public void removeAccount(User user)
    489            throws UnknownEntityException, DataBackendException
    490        {
    491            if (!accountExists(user))
    492            {
    493                throw new UnknownEntityException("The account '" +
    494                                                 user.getName() + "' does not exist");
    495            }
    496            Criteria criteria = new Criteria();
    497            criteria.add(UserPeerManager.getNameColumn(), user.getName());
    498            try
    499            {
    500                UserPeerManager.doDelete(criteria);
    501            }
    502            catch (Exception e)
    503            {
    504                throw new DataBackendException("Failed to remove account '" +
    505                                               user.getName() + "'", e);
    506            }
    507        }
    508    }