View Javadoc

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