View Javadoc

1   package org.apache.turbine.services.security.db;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.Hashtable;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import org.apache.commons.configuration.Configuration;
25  
26  import org.apache.commons.lang.StringUtils;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import org.apache.torque.om.BaseObject;
32  import org.apache.torque.om.ObjectKey;
33  import org.apache.torque.om.Persistent;
34  import org.apache.torque.util.Criteria;
35  
36  import org.apache.turbine.om.security.User;
37  import org.apache.turbine.om.security.peer.TurbineUserPeer;
38  import org.apache.turbine.services.security.TurbineSecurity;
39  import org.apache.turbine.services.security.UserManager;
40  import org.apache.turbine.util.db.map.TurbineMapBuilder;
41  import org.apache.turbine.util.security.DataBackendException;
42  import org.apache.turbine.util.security.EntityExistsException;
43  import org.apache.turbine.util.security.PasswordMismatchException;
44  import org.apache.turbine.util.security.UnknownEntityException;
45  
46  /***
47   * An UserManager performs {@link org.apache.turbine.om.security.User}
48   * objects related tasks on behalf of the
49   * {@link org.apache.turbine.services.security.BaseSecurityService}.
50   *
51   * This implementation uses a relational database for storing user data. It
52   * expects that the User interface implementation will be castable to
53   * {@link org.apache.torque.om.BaseObject}.
54   *
55   * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
56   * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
57   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
58   * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
59   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
60   * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
61   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
62   * @version $Id: DBUserManager.java,v 1.16.2.3 2004/10/08 12:09:43 henning Exp $
63   */
64  public class DBUserManager
65          implements UserManager
66  {
67      /*** Logging */
68      private static Log log = LogFactory.getLog(DBUserManager.class);
69  
70      /***
71       * Initializes the UserManager
72       *
73       * @param conf A Configuration object to init this Manager
74       */
75      public void init(Configuration conf)
76      {
77          // GNDN
78      }
79  
80      /***
81       * Check whether a specified user's account exists.
82       *
83       * The login name is used for looking up the account.
84       *
85       * @param user The user to be checked.
86       * @return true if the specified account exists
87       * @throws DataBackendException if there was an error accessing
88       *         the data backend.
89       */
90      public boolean accountExists(User user)
91              throws DataBackendException
92      {
93          return accountExists(user.getName());
94      }
95  
96      /***
97       * Check whether a specified user's account exists.
98       *
99       * The login name is used for looking up the account.
100      *
101      * @param userName The name of the user to be checked.
102      * @return true if the specified account exists
103      * @throws DataBackendException if there was an error accessing
104      *         the data backend.
105      */
106     public boolean accountExists(String userName)
107             throws DataBackendException
108     {
109         Criteria criteria = new Criteria();
110         criteria.add(TurbineUserPeer.USERNAME, userName);
111         List users;
112         try
113         {
114             users = TurbineUserPeer.doSelect(criteria);
115         }
116         catch (Exception e)
117         {
118             throw new DataBackendException(
119                     "Failed to check account's presence", e);
120         }
121         if (users.size() > 1)
122         {
123             throw new DataBackendException(
124                     "Multiple Users with same username '" + userName + "'");
125         }
126         return (users.size() == 1);
127     }
128 
129     /***
130      * Retrieve a user from persistent storage using username as the
131      * key.
132      *
133      * @param userName the name of the user.
134      * @return an User object.
135      * @exception UnknownEntityException if the user's account does not
136      *            exist in the database.
137      * @exception DataBackendException if there is a problem accessing the
138      *            storage.
139      */
140     public User retrieve(String userName)
141             throws UnknownEntityException, DataBackendException
142     {
143         Criteria criteria = new Criteria();
144         criteria.add(TurbineUserPeer.USERNAME, userName);
145 
146         List users = retrieveList(criteria);
147 
148         if (users.size() > 1)
149         {
150             throw new DataBackendException(
151                     "Multiple Users with same username '" + userName + "'");
152         }
153         if (users.size() == 1)
154         {
155             return (User) users.get(0);
156         }
157         throw new UnknownEntityException("Unknown user '" + userName + "'");
158     }
159 
160     /***
161      * Retrieve a user from persistent storage using the primary key
162      *
163      * @param key The primary key object
164      * @return an User object.
165      * @throws UnknownEntityException if the user's record does not
166      *         exist in the database.
167      * @throws DataBackendException if there is a problem accessing the
168      *         storage.
169      */
170     public User retrieveById(Object key)
171             throws UnknownEntityException, DataBackendException
172     {
173         Criteria criteria = new Criteria();
174         criteria.add(TurbineUserPeer.USER_ID, key);
175 
176         List users = retrieveList(criteria);
177 
178         if (users.size() > 1)
179         {
180             throw new DataBackendException(
181                 "Multiple Users with same unique Key '" + String.valueOf(key) + "'");
182         }
183         if (users.size() == 1)
184         {
185             return (User) users.get(0);
186         }
187         throw new UnknownEntityException("Unknown user with key '" + String.valueOf(key) + "'");
188     }
189 
190     /***
191      * Retrieve a list of users that meet the specified criteria.
192      *
193      * As the keys for the criteria, you should use the constants that
194      * are defined in {@link User} interface, plus the names
195      * of the custom attributes you added to your user representation
196      * in the data storage. Use verbatim names of the attributes -
197      * without table name prefix in case of Torque implementation.
198      *
199      * @param criteria The criteria of selection.
200      * @return a List of users meeting the criteria.
201      * @throws DataBackendException if there is a problem accessing the
202      *         storage.
203      */
204     public List retrieveList(Criteria criteria)
205         throws DataBackendException
206     {
207         for (Iterator keys = criteria.keySet().iterator(); keys.hasNext(); )
208         {
209             String key = (String) keys.next();
210 
211             // set the table name for all attached criterion
212             Criteria.Criterion[] criterion = criteria
213                     .getCriterion(key).getAttachedCriterion();
214 
215             for (int i = 0; i < criterion.length; i++)
216             {
217                 if (StringUtils.isEmpty(criterion[i].getTable()))
218                 {
219                     criterion[i].setTable(TurbineUserPeer.getTableName());
220                 }
221             }
222         }
223         List users = null;
224         try
225         {
226             users = TurbineUserPeer.doSelect(criteria);
227         }
228         catch (Exception e)
229         {
230             throw new DataBackendException("Failed to retrieve users", e);
231         }
232         return users;
233     }
234 
235     /***
236      * Retrieve a set of users that meet the specified criteria.
237      *
238      * As the keys for the criteria, you should use the constants that
239      * are defined in {@link User} interface, plus the names
240      * of the custom attributes you added to your user representation
241      * in the data storage. Use verbatim names of the attributes -
242      * without table name prefix in case of DB implementation.
243      *
244      * @param criteria The criteria of selection.
245      * @return a List of users meeting the criteria.
246      * @throws DataBackendException if there is a problem accessing the
247      *         storage.
248      * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
249      */
250     public User[] retrieve(Criteria criteria)
251             throws DataBackendException
252     {
253         return (User []) retrieveList(criteria).toArray(new User[0]);
254     }
255 
256     /***
257      * Retrieve a user from persistent storage using username as the
258      * key, and authenticate the user. The implementation may chose
259      * to authenticate to the server as the user whose data is being
260      * retrieved.
261      *
262      * @param userName the name of the user.
263      * @param password the user supplied password.
264      * @return an User object.
265      * @exception PasswordMismatchException if the supplied password was
266      *            incorrect.
267      * @exception UnknownEntityException if the user's account does not
268      *            exist in the database.
269      * @exception DataBackendException if there is a problem accessing the
270      *            storage.
271      */
272     public User retrieve(String userName, String password)
273             throws PasswordMismatchException, UnknownEntityException,
274             DataBackendException
275     {
276         User user = retrieve(userName);
277         authenticate(user, password);
278         return user;
279     }
280 
281     /***
282      * Save an User object to persistent storage. User's account is
283      * required to exist in the storage.
284      *
285      * @param user an User object to store.
286      * @exception UnknownEntityException if the user's account does not
287      *            exist in the database.
288      * @exception DataBackendException if there is a problem accessing the
289      *            storage.
290      */
291     public void store(User user)
292             throws UnknownEntityException, DataBackendException
293     {
294         if (!accountExists(user))
295         {
296             throw new UnknownEntityException("The account '" +
297                     user.getName() + "' does not exist");
298         }
299 
300         Criteria criteria = TurbineUserPeer.buildCriteria(user);
301         try
302         {
303             TurbineUserPeer.doUpdate(criteria);
304         }
305         catch (Exception e)
306         {
307             throw new DataBackendException("Failed to save user object", e);
308         }
309     }
310 
311     /***
312      * Saves User data when the session is unbound. The user account is required
313      * to exist in the storage.
314      *
315      * LastLogin, AccessCounter, persistent pull tools, and any data stored
316      * in the permData hashtable that is not mapped to a column will be saved.
317      *
318      * @exception UnknownEntityException if the user's account does not
319      *            exist in the database.
320      * @exception DataBackendException if there is a problem accessing the
321      *            storage.
322      */
323     public void saveOnSessionUnbind(User user)
324             throws UnknownEntityException, DataBackendException
325     {
326         if (!user.hasLoggedIn())
327         {
328             return;
329         }
330 
331         if (!accountExists(user))
332         {
333             throw new UnknownEntityException("The account '" +
334                     user.getName() + "' does not exist");
335         }
336         Criteria crit = new Criteria();
337         if (!((Persistent) user).isNew())
338         {
339             crit.add(TurbineUserPeer.USER_ID, ((Persistent) user).getPrimaryKey());
340         }
341 
342         Hashtable permStorage = (Hashtable) user.getPermStorage().clone();
343         crit.add(TurbineUserPeer.LAST_LOGIN, permStorage.remove(TurbineUserPeer.LAST_LOGIN));
344 
345         // The OBJECT_DATA column only stores data not mapped to a column.  We must
346         // remove all of the extra data and serialize the rest.  Access Counter
347         // is not mapped to a column so it will be serialized into OBJECT_DATA.
348         for (int i = 1; i < TurbineUserPeer.columnNames.length; i++)
349         {
350             if (permStorage.containsKey(TurbineUserPeer.columnNames[i]))
351             {
352                 permStorage.remove(TurbineUserPeer.columnNames[i]);
353             }
354         }
355         crit.add(TurbineUserPeer.OBJECT_DATA, permStorage);
356 
357         try
358         {
359             TurbineUserPeer.doUpdate(crit);
360         }
361         catch (Exception e)
362         {
363             throw new DataBackendException("Failed to save user object", e);
364         }
365 
366     }
367 
368     /***
369      * Authenticate an User with the specified password. If authentication
370      * is successful the method returns nothing. If there are any problems,
371      * exception was thrown.
372      *
373      * @param user an User object to authenticate.
374      * @param password the user supplied password.
375      * @exception PasswordMismatchException if the supplied password was
376      *            incorrect.
377      * @exception UnknownEntityException if the user's account does not
378      *            exist in the database.
379      * @exception DataBackendException if there is a problem accessing the
380      *            storage.
381      */
382     public void authenticate(User user, String password)
383             throws PasswordMismatchException, UnknownEntityException,
384             DataBackendException
385     {
386         if (!accountExists(user))
387         {
388             throw new UnknownEntityException("The account '" +
389                     user.getName() + "' does not exist");
390         }
391 
392         // log.debug("Supplied Pass: " + password);
393         // log.debug("User Pass: " + user.getPassword());
394 
395         /*
396          * Unix crypt needs the existing, encrypted password text as
397          * salt for checking the supplied password. So we supply it
398          * into the checkPassword routine
399          */
400 
401         if (!TurbineSecurity.checkPassword(password, user.getPassword()))
402         {
403             throw new PasswordMismatchException("The passwords do not match");
404         }
405     }
406 
407     /***
408      * Change the password for an User. The user must have supplied the
409      * old password to allow the change.
410      *
411      * @param user an User to change password for.
412      * @param oldPassword The old password to verify
413      * @param newPassword The new password to set
414      * @exception PasswordMismatchException if the supplied password was
415      *            incorrect.
416      * @exception UnknownEntityException if the user's account does not
417      *            exist in the database.
418      * @exception DataBackendException if there is a problem accessing the
419      *            storage.
420      */
421     public void changePassword(User user, String oldPassword,
422                                String newPassword)
423             throws PasswordMismatchException, UnknownEntityException,
424             DataBackendException
425     {
426         if (!accountExists(user))
427         {
428             throw new UnknownEntityException("The account '" +
429                     user.getName() + "' does not exist");
430         }
431 
432         if (!TurbineSecurity.checkPassword(oldPassword, user.getPassword()))
433         {
434             throw new PasswordMismatchException(
435                     "The supplied old password for '" + user.getName() +
436                     "' was incorrect");
437         }
438         user.setPassword(TurbineSecurity.encryptPassword(newPassword));
439         // save the changes in the database imediately, to prevent the password
440         // being 'reverted' to the old value if the user data is lost somehow
441         // before it is saved at session's expiry.
442         store(user);
443     }
444 
445     /***
446      * Forcibly sets new password for an User.
447      *
448      * This is supposed by the administrator to change the forgotten or
449      * compromised passwords. Certain implementatations of this feature
450      * would require administrative level access to the authenticating
451      * server / program.
452      *
453      * @param user an User to change password for.
454      * @param password the new password.
455      * @exception UnknownEntityException if the user's record does not
456      *            exist in the database.
457      * @exception DataBackendException if there is a problem accessing the
458      *            storage.
459      */
460     public void forcePassword(User user, String password)
461             throws UnknownEntityException, DataBackendException
462     {
463         if (!accountExists(user))
464         {
465             throw new UnknownEntityException("The account '" +
466                     user.getName() + "' does not exist");
467         }
468         user.setPassword(TurbineSecurity.encryptPassword(password));
469         // save the changes in the database immediately, to prevent the
470         // password being 'reverted' to the old value if the user data
471         // is lost somehow before it is saved at session's expiry.
472         store(user);
473     }
474 
475     /***
476      * Creates new user account with specified attributes.
477      *
478      * @param user The object describing account to be created.
479      * @param initialPassword the password for the new account
480      * @throws DataBackendException if there was an error accessing
481      the data backend.
482      * @throws EntityExistsException if the user account already exists.
483      */
484     public void createAccount(User user, String initialPassword)
485             throws EntityExistsException, DataBackendException
486     {
487         if (StringUtils.isEmpty(user.getName()))
488         {
489             throw new DataBackendException("Could not create "
490                     + "an user with empty name!");
491         }
492 
493         if (accountExists(user))
494         {
495             throw new EntityExistsException("The account '" +
496                     user.getName() + "' already exists");
497         }
498         user.setPassword(TurbineSecurity.encryptPassword(initialPassword));
499 
500         Criteria criteria = TurbineUserPeer.buildCriteria(user);
501         try
502         {
503             // perform the insert to the database
504             ObjectKey pk = TurbineUserPeer.doInsert(criteria);
505 
506             // update the user object with the primary key
507             TurbineMapBuilder mapbuilder = (TurbineMapBuilder)
508                     TurbineUserPeer.getMapBuilder("org.apache.turbine.util.db.map.TurbineMapBuilder");
509             user.setPerm(mapbuilder.getUserId(), pk);
510             ((BaseObject) user).setPrimaryKey(pk);
511         }
512         catch (Exception e)
513         {
514             throw new DataBackendException("Failed to create account '" +
515                     user.getName() + "'", e);
516         }
517     }
518 
519     /***
520      * Removes an user account from the system.
521      *
522      * @param user the object describing the account to be removed.
523      * @throws DataBackendException if there was an error accessing
524      the data backend.
525      * @throws UnknownEntityException if the user account is not present.
526      */
527     public void removeAccount(User user)
528             throws UnknownEntityException, DataBackendException
529     {
530         if (!accountExists(user))
531         {
532             throw new UnknownEntityException("The account '" +
533                     user.getName() + "' does not exist");
534         }
535         Criteria criteria = new Criteria();
536         criteria.add(TurbineUserPeer.USERNAME, user.getName());
537         try
538         {
539             TurbineUserPeer.doDelete(criteria);
540         }
541         catch (Exception e)
542         {
543             throw new DataBackendException("Failed to remove account '" +
544                     user.getName() + "'", e);
545         }
546     }
547 }