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