View Javadoc

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