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    }