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 1096130 2011-04-23 10:37:19Z ludwig $
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(Object 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(Object criteria)
210         throws DataBackendException
211     {
212         if (criteria instanceof Criteria)
213         {
214             Criteria c = (Criteria)criteria;
215             for (Iterator keys = c.keySet().iterator(); keys.hasNext(); )
216             {
217                 String key = (String) keys.next();
218     
219                 // set the table name for all attached criterion
220                 Criteria.Criterion[] criterion = 
221                     c.getCriterion(key).getAttachedCriterion();
222     
223                 for (int i = 0; i < criterion.length; i++)
224                 {
225                     if (StringUtils.isEmpty(criterion[i].getTable()))
226                     {
227                         criterion[i].setTable(UserPeerManager.getTableName());
228                     }
229                 }
230             }
231             List users = null;
232             try
233             {
234                 users = UserPeerManager.doSelect(c);
235             }
236             catch (Exception e)
237             {
238                 throw new DataBackendException("Failed to retrieve users", e);
239             }
240             return users;
241         }
242         else
243         {
244             throw new DataBackendException("Failed to retrieve users with invalid criteria");
245         }
246     }
247 
248     /**
249      * Retrieve a user from persistent storage using username as the
250      * key, and authenticate the user. The implementation may chose
251      * to authenticate to the server as the user whose data is being
252      * retrieved.
253      *
254      * @param userName the name of the user.
255      * @param password the user supplied password.
256      * @return an User object.
257      * @exception PasswordMismatchException if the supplied password was
258      *            incorrect.
259      * @exception UnknownEntityException if the user's account does not
260      *            exist in the database.
261      * @exception DataBackendException if there is a problem accessing the
262      *            storage.
263      */
264     public User retrieve(String userName, String password)
265         throws PasswordMismatchException, UnknownEntityException,
266                DataBackendException
267     {
268         User user = retrieve(userName);
269         authenticate(user, password);
270         return user;
271     }
272 
273     /**
274      * Save an User object to persistent storage. User's account is
275      * required to exist in the storage.
276      *
277      * @param user an User object to store.
278      * @exception UnknownEntityException if the user's account does not
279      *            exist in the database.
280      * @exception DataBackendException if there is a problem accessing the
281      *            storage.
282      */
283     public void store(User user)
284         throws UnknownEntityException, DataBackendException
285     {
286         if (!accountExists(user))
287         {
288             throw new UnknownEntityException("The account '" +
289                                              user.getName() + "' does not exist");
290         }
291 
292         try
293         {
294             // this is to mimic the old behavior of the method, the user
295             // should be new that is passed to this method.  It would be
296             // better if this was checked, but the original code did not
297             // care about the user's state, so we set it to be appropriate
298             ((Persistent) user).setNew(false);
299             ((Persistent) user).setModified(true);
300             ((Persistent) user).save();
301         }
302         catch (Exception e)
303         {
304             throw new DataBackendException("Failed to save user object", e);
305         }
306     }
307 
308     /**
309      * Saves User data when the session is unbound. The user account is required
310      * to exist in the storage.
311      *
312      * LastLogin, AccessCounter, persistent pull tools, and any data stored
313      * in the permData hashtable that is not mapped to a column will be saved.
314      *
315      * @exception UnknownEntityException if the user's account does not
316      *            exist in the database.
317      * @exception DataBackendException if there is a problem accessing the
318      *            storage.
319      */
320     public void saveOnSessionUnbind(User user)
321         throws UnknownEntityException, DataBackendException
322     {
323         if (!user.hasLoggedIn())
324         {
325             return;
326         }
327         store(user);
328     }
329 
330 
331     /**
332      * Authenticate an User with the specified password. If authentication
333      * is successful the method returns nothing. If there are any problems,
334      * exception was thrown.
335      *
336      * @param user an User object to authenticate.
337      * @param password the user supplied password.
338      * @exception PasswordMismatchException if the supplied password was
339      *            incorrect.
340      * @exception UnknownEntityException if the user's account does not
341      *            exist in the database.
342      * @exception DataBackendException if there is a problem accessing the
343      *            storage.
344      */
345     public void authenticate(User user, String password)
346         throws PasswordMismatchException, UnknownEntityException,
347                DataBackendException
348     {
349         if (!accountExists(user))
350         {
351             throw new UnknownEntityException("The account '" +
352                                              user.getName() + "' does not exist");
353         }
354 
355         // log.debug("Supplied Pass: " + password);
356         // log.debug("User Pass: " + user.getPassword());
357 
358         /*
359          * Unix crypt needs the existing, encrypted password text as
360          * salt for checking the supplied password. So we supply it
361          * into the checkPassword routine
362          */
363 
364         if (!TurbineSecurity.checkPassword(password, user.getPassword()))
365         {
366             throw new PasswordMismatchException("The passwords do not match");
367         }
368     }
369 
370     /**
371      * Change the password for an User. The user must have supplied the
372      * old password to allow the change.
373      *
374      * @param user an User to change password for.
375      * @param oldPassword The old password to verify
376      * @param newPassword The new password to set
377      * @exception PasswordMismatchException if the supplied password was
378      *            incorrect.
379      * @exception UnknownEntityException if the user's account does not
380      *            exist in the database.
381      * @exception DataBackendException if there is a problem accessing the
382      *            storage.
383      */
384     public void changePassword(User user, String oldPassword,
385                                String newPassword)
386         throws PasswordMismatchException, UnknownEntityException,
387                DataBackendException
388     {
389         if (!accountExists(user))
390         {
391             throw new UnknownEntityException("The account '" +
392                                              user.getName() + "' does not exist");
393         }
394 
395         if (!TurbineSecurity.checkPassword(oldPassword, user.getPassword()))
396         {
397             throw new PasswordMismatchException(
398                 "The supplied old password for '" + user.getName() +
399                 "' was incorrect");
400         }
401         user.setPassword(TurbineSecurity.encryptPassword(newPassword));
402         // save the changes in the database imediately, to prevent the password
403         // being 'reverted' to the old value if the user data is lost somehow
404         // before it is saved at session's expiry.
405         store(user);
406     }
407 
408     /**
409      * Forcibly sets new password for an User.
410      *
411      * This is supposed by the administrator to change the forgotten or
412      * compromised passwords. Certain implementatations of this feature
413      * would require administrative level access to the authenticating
414      * server / program.
415      *
416      * @param user an User to change password for.
417      * @param password the new password.
418      * @exception UnknownEntityException if the user's record does not
419      *            exist in the database.
420      * @exception DataBackendException if there is a problem accessing the
421      *            storage.
422      */
423     public void forcePassword(User user, String password)
424         throws UnknownEntityException, DataBackendException
425     {
426         if (!accountExists(user))
427         {
428             throw new UnknownEntityException("The account '" +
429                                              user.getName() + "' does not exist");
430         }
431         user.setPassword(TurbineSecurity.encryptPassword(password));
432         // save the changes in the database immediately, to prevent the
433         // password being 'reverted' to the old value if the user data
434         // is lost somehow before it is saved at session's expiry.
435         store(user);
436     }
437 
438     /**
439      * Creates new user account with specified attributes.
440      *
441      * @param user The object describing account to be created.
442      * @param initialPassword the password for the new account
443      * @throws DataBackendException if there was an error accessing
444      the data backend.
445      * @throws EntityExistsException if the user account already exists.
446      */
447     public void createAccount(User user, String initialPassword)
448         throws EntityExistsException, DataBackendException
449     {
450         if(StringUtils.isEmpty(user.getName()))
451         {
452             throw new DataBackendException("Could not create "
453                                            + "an user with empty name!");
454         }
455 
456         if (accountExists(user))
457         {
458             throw new EntityExistsException("The account '" +
459                                             user.getName() + "' already exists");
460         }
461         user.setPassword(TurbineSecurity.encryptPassword(initialPassword));
462 
463         try
464         {
465             // this is to mimic the old behavior of the method, the user
466             // should be new that is passed to this method.  It would be
467             // better if this was checked, but the original code did not
468             // care about the user's state, so we set it to be appropriate
469             ((Persistent) user).setNew(true);
470             ((Persistent) user).setModified(true);
471             ((Persistent) user).save();
472         }
473         catch (Exception e)
474         {
475             throw new DataBackendException("Failed to create account '" +
476                                            user.getName() + "'", e);
477         }
478     }
479 
480     /**
481      * Removes an user account from the system.
482      *
483      * @param user the object describing the account to be removed.
484      * @throws DataBackendException if there was an error accessing
485      the data backend.
486      * @throws UnknownEntityException if the user account is not present.
487      */
488     public void removeAccount(User user)
489         throws UnknownEntityException, DataBackendException
490     {
491         if (!accountExists(user))
492         {
493             throw new UnknownEntityException("The account '" +
494                                              user.getName() + "' does not exist");
495         }
496         Criteria criteria = new Criteria();
497         criteria.add(UserPeerManager.getNameColumn(), user.getName());
498         try
499         {
500             UserPeerManager.doDelete(criteria);
501         }
502         catch (Exception e)
503         {
504             throw new DataBackendException("Failed to remove account '" +
505                                            user.getName() + "'", e);
506         }
507     }
508 }