View Javadoc

1   package org.apache.turbine.services.security.torque;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Iterator;
23  import java.util.List;
24  
25  import org.apache.commons.configuration.Configuration;
26  import org.apache.commons.lang.StringUtils;
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.services.InitializationException;
31  import org.apache.turbine.services.security.TurbineSecurity;
32  import org.apache.turbine.services.security.UserManager;
33  import org.apache.turbine.util.security.DataBackendException;
34  import org.apache.turbine.util.security.EntityExistsException;
35  import org.apache.turbine.util.security.PasswordMismatchException;
36  import org.apache.turbine.util.security.UnknownEntityException;
37  
38  /***
39   * An UserManager performs {@link org.apache.turbine.om.security.User}
40   * objects related tasks on behalf of the
41   * {@link org.apache.turbine.services.security.BaseSecurityService}.
42   *
43   * This implementation uses a relational database for storing user data. It
44   * expects that the User interface implementation will be castable to
45   * {@link org.apache.torque.om.BaseObject}.
46   *
47   * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
48   * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
49   * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
50   * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
51   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
52   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
53   * @version $Id: TorqueUserManager.java 534527 2007-05-02 16:10:59Z tv $
54   */
55  public class TorqueUserManager
56      implements UserManager
57  {
58      /***
59       * Initializes the UserManager
60       *
61       * @param conf A Configuration object to init this Manager
62       *
63       * @throws InitializationException When something went wrong.
64       */
65      public void init(Configuration conf)
66          throws InitializationException
67      {
68          UserPeerManager.init(conf);
69      }
70  
71      /***
72       * Check whether a specified user's account exists.
73       *
74       * The login name is used for looking up the account.
75       *
76       * @param user The user to be checked.
77       * @return true if the specified account exists
78       * @throws DataBackendException if there was an error accessing
79       *         the data backend.
80       */
81      public boolean accountExists(User user)
82          throws DataBackendException
83      {
84          return accountExists(user.getName());
85      }
86  
87      /***
88       * Check whether a specified user's account exists.
89       *
90       * The login name is used for looking up the account.
91       *
92       * @param userName The name of the user to be checked.
93       * @return true if the specified account exists
94       * @throws DataBackendException if there was an error accessing
95       *         the data backend.
96       */
97      public boolean accountExists(String userName)
98          throws DataBackendException
99      {
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(Criteria 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(Criteria criteria)
210         throws DataBackendException
211     {
212         for (Iterator keys = criteria.keySet().iterator(); keys.hasNext(); )
213         {
214             String key = (String) keys.next();
215 
216             // set the table name for all attached criterion
217             Criteria.Criterion[] criterion = criteria
218                 .getCriterion(key).getAttachedCriterion();
219 
220             for (int i = 0; i < criterion.length; i++)
221             {
222                 if (StringUtils.isEmpty(criterion[i].getTable()))
223                 {
224                     criterion[i].setTable(UserPeerManager.getTableName());
225                 }
226             }
227         }
228         List users = null;
229         try
230         {
231             users = UserPeerManager.doSelect(criteria);
232         }
233         catch (Exception e)
234         {
235             throw new DataBackendException("Failed to retrieve users", e);
236         }
237         return users;
238     }
239 
240     /***
241      * Retrieve a user from persistent storage using username as the
242      * key, and authenticate the user. The implementation may chose
243      * to authenticate to the server as the user whose data is being
244      * retrieved.
245      *
246      * @param userName the name of the user.
247      * @param password the user supplied password.
248      * @return an User object.
249      * @exception PasswordMismatchException if the supplied password was
250      *            incorrect.
251      * @exception UnknownEntityException if the user's account does not
252      *            exist in the database.
253      * @exception DataBackendException if there is a problem accessing the
254      *            storage.
255      */
256     public User retrieve(String userName, String password)
257         throws PasswordMismatchException, UnknownEntityException,
258                DataBackendException
259     {
260         User user = retrieve(userName);
261         authenticate(user, password);
262         return user;
263     }
264 
265     /***
266      * Save an User object to persistent storage. User's account is
267      * required to exist in the storage.
268      *
269      * @param user an User object to store.
270      * @exception UnknownEntityException if the user's account does not
271      *            exist in the database.
272      * @exception DataBackendException if there is a problem accessing the
273      *            storage.
274      */
275     public void store(User user)
276         throws UnknownEntityException, DataBackendException
277     {
278         if (!accountExists(user))
279         {
280             throw new UnknownEntityException("The account '" +
281                                              user.getName() + "' does not exist");
282         }
283 
284         try
285         {
286             // this is to mimic the old behavior of the method, the user
287             // should be new that is passed to this method.  It would be
288             // better if this was checked, but the original code did not
289             // care about the user's state, so we set it to be appropriate
290             ((Persistent) user).setNew(false);
291             ((Persistent) user).setModified(true);
292             ((Persistent) user).save();
293         }
294         catch (Exception e)
295         {
296             throw new DataBackendException("Failed to save user object", e);
297         }
298     }
299 
300     /***
301      * Saves User data when the session is unbound. The user account is required
302      * to exist in the storage.
303      *
304      * LastLogin, AccessCounter, persistent pull tools, and any data stored
305      * in the permData hashtable that is not mapped to a column will be saved.
306      *
307      * @exception UnknownEntityException if the user's account does not
308      *            exist in the database.
309      * @exception DataBackendException if there is a problem accessing the
310      *            storage.
311      */
312     public void saveOnSessionUnbind(User user)
313         throws UnknownEntityException, DataBackendException
314     {
315         if (!user.hasLoggedIn())
316         {
317             return;
318         }
319         store(user);
320     }
321 
322 
323     /***
324      * Authenticate an User with the specified password. If authentication
325      * is successful the method returns nothing. If there are any problems,
326      * exception was thrown.
327      *
328      * @param user an User object to authenticate.
329      * @param password the user supplied password.
330      * @exception PasswordMismatchException if the supplied password was
331      *            incorrect.
332      * @exception UnknownEntityException if the user's account does not
333      *            exist in the database.
334      * @exception DataBackendException if there is a problem accessing the
335      *            storage.
336      */
337     public void authenticate(User user, String password)
338         throws PasswordMismatchException, UnknownEntityException,
339                DataBackendException
340     {
341         if (!accountExists(user))
342         {
343             throw new UnknownEntityException("The account '" +
344                                              user.getName() + "' does not exist");
345         }
346 
347         // log.debug("Supplied Pass: " + password);
348         // log.debug("User Pass: " + user.getPassword());
349 
350         /*
351          * Unix crypt needs the existing, encrypted password text as
352          * salt for checking the supplied password. So we supply it
353          * into the checkPassword routine
354          */
355 
356         if (!TurbineSecurity.checkPassword(password, user.getPassword()))
357         {
358             throw new PasswordMismatchException("The passwords do not match");
359         }
360     }
361 
362     /***
363      * Change the password for an User. The user must have supplied the
364      * old password to allow the change.
365      *
366      * @param user an User to change password for.
367      * @param oldPassword The old password to verify
368      * @param newPassword The new password to set
369      * @exception PasswordMismatchException if the supplied password was
370      *            incorrect.
371      * @exception UnknownEntityException if the user's account does not
372      *            exist in the database.
373      * @exception DataBackendException if there is a problem accessing the
374      *            storage.
375      */
376     public void changePassword(User user, String oldPassword,
377                                String newPassword)
378         throws PasswordMismatchException, UnknownEntityException,
379                DataBackendException
380     {
381         if (!accountExists(user))
382         {
383             throw new UnknownEntityException("The account '" +
384                                              user.getName() + "' does not exist");
385         }
386 
387         if (!TurbineSecurity.checkPassword(oldPassword, user.getPassword()))
388         {
389             throw new PasswordMismatchException(
390                 "The supplied old password for '" + user.getName() +
391                 "' was incorrect");
392         }
393         user.setPassword(TurbineSecurity.encryptPassword(newPassword));
394         // save the changes in the database imediately, to prevent the password
395         // being 'reverted' to the old value if the user data is lost somehow
396         // before it is saved at session's expiry.
397         store(user);
398     }
399 
400     /***
401      * Forcibly sets new password for an User.
402      *
403      * This is supposed by the administrator to change the forgotten or
404      * compromised passwords. Certain implementatations of this feature
405      * would require administrative level access to the authenticating
406      * server / program.
407      *
408      * @param user an User to change password for.
409      * @param password the new password.
410      * @exception UnknownEntityException if the user's record does not
411      *            exist in the database.
412      * @exception DataBackendException if there is a problem accessing the
413      *            storage.
414      */
415     public void forcePassword(User user, String password)
416         throws UnknownEntityException, DataBackendException
417     {
418         if (!accountExists(user))
419         {
420             throw new UnknownEntityException("The account '" +
421                                              user.getName() + "' does not exist");
422         }
423         user.setPassword(TurbineSecurity.encryptPassword(password));
424         // save the changes in the database immediately, to prevent the
425         // password being 'reverted' to the old value if the user data
426         // is lost somehow before it is saved at session's expiry.
427         store(user);
428     }
429 
430     /***
431      * Creates new user account with specified attributes.
432      *
433      * @param user The object describing account to be created.
434      * @param initialPassword the password for the new account
435      * @throws DataBackendException if there was an error accessing
436      the data backend.
437      * @throws EntityExistsException if the user account already exists.
438      */
439     public void createAccount(User user, String initialPassword)
440         throws EntityExistsException, DataBackendException
441     {
442         if(StringUtils.isEmpty(user.getName()))
443         {
444             throw new DataBackendException("Could not create "
445                                            + "an user with empty name!");
446         }
447 
448         if (accountExists(user))
449         {
450             throw new EntityExistsException("The account '" +
451                                             user.getName() + "' already exists");
452         }
453         user.setPassword(TurbineSecurity.encryptPassword(initialPassword));
454 
455         try
456         {
457             // this is to mimic the old behavior of the method, the user
458             // should be new that is passed to this method.  It would be
459             // better if this was checked, but the original code did not
460             // care about the user's state, so we set it to be appropriate
461             ((Persistent) user).setNew(true);
462             ((Persistent) user).setModified(true);
463             ((Persistent) user).save();
464         }
465         catch (Exception e)
466         {
467             throw new DataBackendException("Failed to create account '" +
468                                            user.getName() + "'", e);
469         }
470     }
471 
472     /***
473      * Removes an user account from the system.
474      *
475      * @param user the object describing the account to be removed.
476      * @throws DataBackendException if there was an error accessing
477      the data backend.
478      * @throws UnknownEntityException if the user account is not present.
479      */
480     public void removeAccount(User user)
481         throws UnknownEntityException, DataBackendException
482     {
483         if (!accountExists(user))
484         {
485             throw new UnknownEntityException("The account '" +
486                                              user.getName() + "' does not exist");
487         }
488         Criteria criteria = new Criteria();
489         criteria.add(UserPeerManager.getNameColumn(), user.getName());
490         try
491         {
492             UserPeerManager.doDelete(criteria);
493         }
494         catch (Exception e)
495         {
496             throw new DataBackendException("Failed to remove account '" +
497                                            user.getName() + "'", e);
498         }
499     }
500 }