View Javadoc

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