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 }