001: package org.apache.turbine.services.security.db;
002:
003: /*
004: * Copyright 2001-2005 The Apache Software Foundation.
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License")
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.util.Hashtable;
020: import java.util.Iterator;
021: import java.util.List;
022:
023: import org.apache.commons.configuration.Configuration;
024: import org.apache.commons.lang.StringUtils;
025: import org.apache.torque.om.BaseObject;
026: import org.apache.torque.om.ObjectKey;
027: import org.apache.torque.om.Persistent;
028: import org.apache.torque.util.Criteria;
029: import org.apache.turbine.om.security.User;
030: import org.apache.turbine.om.security.peer.TurbineUserPeer;
031: import org.apache.turbine.services.security.TurbineSecurity;
032: import org.apache.turbine.services.security.UserManager;
033: import org.apache.turbine.util.db.map.TurbineMapBuilder;
034: import org.apache.turbine.util.security.DataBackendException;
035: import org.apache.turbine.util.security.EntityExistsException;
036: import org.apache.turbine.util.security.PasswordMismatchException;
037: import org.apache.turbine.util.security.UnknownEntityException;
038:
039: /**
040: * An UserManager performs {@link org.apache.turbine.om.security.User}
041: * objects related tasks on behalf of the
042: * {@link org.apache.turbine.services.security.BaseSecurityService}.
043: *
044: * This implementation uses a relational database for storing user data. It
045: * expects that the User interface implementation will be castable to
046: * {@link org.apache.torque.om.BaseObject}.
047: *
048: * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
049: * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
050: * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
051: * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
052: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
053: * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
054: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
055: * @version $Id: DBUserManager.java 278824 2005-09-05 20:01:15Z henning $
056: */
057: public class DBUserManager implements UserManager {
058: /**
059: * Initializes the UserManager
060: *
061: * @param conf A Configuration object to init this Manager
062: */
063: public void init(Configuration conf) {
064: // GNDN
065: }
066:
067: /**
068: * Check whether a specified user's account exists.
069: *
070: * The login name is used for looking up the account.
071: *
072: * @param user The user to be checked.
073: * @return true if the specified account exists
074: * @throws DataBackendException if there was an error accessing
075: * the data backend.
076: */
077: public boolean accountExists(User user) throws DataBackendException {
078: return accountExists(user.getName());
079: }
080:
081: /**
082: * Check whether a specified user's account exists.
083: *
084: * The login name is used for looking up the account.
085: *
086: * @param userName The name of the user to be checked.
087: * @return true if the specified account exists
088: * @throws DataBackendException if there was an error accessing
089: * the data backend.
090: */
091: public boolean accountExists(String userName)
092: throws DataBackendException {
093: Criteria criteria = new Criteria();
094: criteria.add(TurbineUserPeer.USERNAME, userName);
095: List users;
096: try {
097: users = TurbineUserPeer.doSelect(criteria);
098: } catch (Exception e) {
099: throw new DataBackendException(
100: "Failed to check account's presence", e);
101: }
102: if (users.size() > 1) {
103: throw new DataBackendException(
104: "Multiple Users with same username '" + userName
105: + "'");
106: }
107: return (users.size() == 1);
108: }
109:
110: /**
111: * Retrieve a user from persistent storage using username as the
112: * key.
113: *
114: * @param userName the name of the user.
115: * @return an User object.
116: * @exception UnknownEntityException if the user's account does not
117: * exist in the database.
118: * @exception DataBackendException if there is a problem accessing the
119: * storage.
120: */
121: public User retrieve(String userName)
122: throws UnknownEntityException, DataBackendException {
123: Criteria criteria = new Criteria();
124: criteria.add(TurbineUserPeer.USERNAME, userName);
125:
126: List users = retrieveList(criteria);
127:
128: if (users.size() > 1) {
129: throw new DataBackendException(
130: "Multiple Users with same username '" + userName
131: + "'");
132: }
133: if (users.size() == 1) {
134: return (User) users.get(0);
135: }
136: throw new UnknownEntityException("Unknown user '" + userName
137: + "'");
138: }
139:
140: /**
141: * Retrieve a user from persistent storage using the primary key
142: *
143: * @param key The primary key object
144: * @return an User object.
145: * @throws UnknownEntityException if the user's record does not
146: * exist in the database.
147: * @throws DataBackendException if there is a problem accessing the
148: * storage.
149: */
150: public User retrieveById(Object key) throws UnknownEntityException,
151: DataBackendException {
152: Criteria criteria = new Criteria();
153: criteria.add(TurbineUserPeer.USER_ID, key);
154:
155: List users = retrieveList(criteria);
156:
157: if (users.size() > 1) {
158: throw new DataBackendException(
159: "Multiple Users with same unique Key '"
160: + String.valueOf(key) + "'");
161: }
162: if (users.size() == 1) {
163: return (User) users.get(0);
164: }
165: throw new UnknownEntityException("Unknown user with key '"
166: + String.valueOf(key) + "'");
167: }
168:
169: /**
170: * Retrieve a list of users that meet the specified criteria.
171: *
172: * As the keys for the criteria, you should use the constants that
173: * are defined in {@link User} interface, plus the names
174: * of the custom attributes you added to your user representation
175: * in the data storage. Use verbatim names of the attributes -
176: * without table name prefix in case of Torque implementation.
177: *
178: * @param criteria The criteria of selection.
179: * @return a List of users meeting the criteria.
180: * @throws DataBackendException if there is a problem accessing the
181: * storage.
182: */
183: public List retrieveList(Criteria criteria)
184: throws DataBackendException {
185: for (Iterator keys = criteria.keySet().iterator(); keys
186: .hasNext();) {
187: String key = (String) keys.next();
188:
189: // set the table name for all attached criterion
190: Criteria.Criterion[] criterion = criteria.getCriterion(key)
191: .getAttachedCriterion();
192:
193: for (int i = 0; i < criterion.length; i++) {
194: if (StringUtils.isEmpty(criterion[i].getTable())) {
195: criterion[i].setTable(TurbineUserPeer
196: .getTableName());
197: }
198: }
199: }
200: List users = null;
201: try {
202: users = TurbineUserPeer.doSelect(criteria);
203: } catch (Exception e) {
204: throw new DataBackendException("Failed to retrieve users",
205: e);
206: }
207: return users;
208: }
209:
210: /**
211: * Retrieve a set of users that meet the specified criteria.
212: *
213: * As the keys for the criteria, you should use the constants that
214: * are defined in {@link User} interface, plus the names
215: * of the custom attributes you added to your user representation
216: * in the data storage. Use verbatim names of the attributes -
217: * without table name prefix in case of DB implementation.
218: *
219: * @param criteria The criteria of selection.
220: * @return a List of users meeting the criteria.
221: * @throws DataBackendException if there is a problem accessing the
222: * storage.
223: * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
224: */
225: public User[] retrieve(Criteria criteria)
226: throws DataBackendException {
227: return (User[]) retrieveList(criteria).toArray(new User[0]);
228: }
229:
230: /**
231: * Retrieve a user from persistent storage using username as the
232: * key, and authenticate the user. The implementation may chose
233: * to authenticate to the server as the user whose data is being
234: * retrieved.
235: *
236: * @param userName the name of the user.
237: * @param password the user supplied password.
238: * @return an User object.
239: * @exception PasswordMismatchException if the supplied password was
240: * incorrect.
241: * @exception UnknownEntityException if the user's account does not
242: * exist in the database.
243: * @exception DataBackendException if there is a problem accessing the
244: * storage.
245: */
246: public User retrieve(String userName, String password)
247: throws PasswordMismatchException, UnknownEntityException,
248: DataBackendException {
249: User user = retrieve(userName);
250: authenticate(user, password);
251: return user;
252: }
253:
254: /**
255: * Save an User object to persistent storage. User's account is
256: * required to exist in the storage.
257: *
258: * @param user an User object to store.
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 void store(User user) throws UnknownEntityException,
265: DataBackendException {
266: if (!accountExists(user)) {
267: throw new UnknownEntityException("The account '"
268: + user.getName() + "' does not exist");
269: }
270:
271: Criteria criteria = TurbineUserPeer.buildCriteria(user);
272: try {
273: TurbineUserPeer.doUpdate(criteria);
274: } catch (Exception e) {
275: throw new DataBackendException(
276: "Failed to save user object", e);
277: }
278: }
279:
280: /**
281: * Saves User data when the session is unbound. The user account is required
282: * to exist in the storage.
283: *
284: * LastLogin, AccessCounter, persistent pull tools, and any data stored
285: * in the permData hashtable that is not mapped to a column will be saved.
286: *
287: * @exception UnknownEntityException if the user's account does not
288: * exist in the database.
289: * @exception DataBackendException if there is a problem accessing the
290: * storage.
291: */
292: public void saveOnSessionUnbind(User user)
293: throws UnknownEntityException, DataBackendException {
294: if (!user.hasLoggedIn()) {
295: return;
296: }
297:
298: if (!accountExists(user)) {
299: throw new UnknownEntityException("The account '"
300: + user.getName() + "' does not exist");
301: }
302: Criteria crit = new Criteria();
303: if (!((Persistent) user).isNew()) {
304: crit.add(TurbineUserPeer.USER_ID, ((Persistent) user)
305: .getPrimaryKey());
306: }
307:
308: Hashtable permStorage = (Hashtable) user.getPermStorage()
309: .clone();
310: crit.add(TurbineUserPeer.LAST_LOGIN, permStorage
311: .remove(TurbineUserPeer.LAST_LOGIN));
312:
313: // The OBJECT_DATA column only stores data not mapped to a column. We must
314: // remove all of the extra data and serialize the rest. Access Counter
315: // is not mapped to a column so it will be serialized into OBJECT_DATA.
316: for (int i = 1; i < TurbineUserPeer.columnNames.length; i++) {
317: if (permStorage.containsKey(TurbineUserPeer.columnNames[i])) {
318: permStorage.remove(TurbineUserPeer.columnNames[i]);
319: }
320: }
321: crit.add(TurbineUserPeer.OBJECT_DATA, permStorage);
322:
323: try {
324: TurbineUserPeer.doUpdate(crit);
325: } catch (Exception e) {
326: throw new DataBackendException(
327: "Failed to save user object", e);
328: }
329:
330: }
331:
332: /**
333: * Authenticate an User with the specified password. If authentication
334: * is successful the method returns nothing. If there are any problems,
335: * exception was thrown.
336: *
337: * @param user an User object to authenticate.
338: * @param password the user supplied password.
339: * @exception PasswordMismatchException if the supplied password was
340: * incorrect.
341: * @exception UnknownEntityException if the user's account does not
342: * exist in the database.
343: * @exception DataBackendException if there is a problem accessing the
344: * storage.
345: */
346: public void authenticate(User user, String password)
347: throws PasswordMismatchException, UnknownEntityException,
348: DataBackendException {
349: if (!accountExists(user)) {
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
364: .checkPassword(password, user.getPassword())) {
365: throw new PasswordMismatchException(
366: "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) throws PasswordMismatchException,
386: UnknownEntityException, DataBackendException {
387: if (!accountExists(user)) {
388: throw new UnknownEntityException("The account '"
389: + user.getName() + "' does not exist");
390: }
391:
392: if (!TurbineSecurity.checkPassword(oldPassword, user
393: .getPassword())) {
394: throw new PasswordMismatchException(
395: "The supplied old password for '" + user.getName()
396: + "' was incorrect");
397: }
398: user.setPassword(TurbineSecurity.encryptPassword(newPassword));
399: // save the changes in the database imediately, to prevent the password
400: // being 'reverted' to the old value if the user data is lost somehow
401: // before it is saved at session's expiry.
402: store(user);
403: }
404:
405: /**
406: * Forcibly sets new password for an User.
407: *
408: * This is supposed by the administrator to change the forgotten or
409: * compromised passwords. Certain implementatations of this feature
410: * would require administrative level access to the authenticating
411: * server / program.
412: *
413: * @param user an User to change password for.
414: * @param password the new password.
415: * @exception UnknownEntityException if the user's record does not
416: * exist in the database.
417: * @exception DataBackendException if there is a problem accessing the
418: * storage.
419: */
420: public void forcePassword(User user, String password)
421: throws UnknownEntityException, DataBackendException {
422: if (!accountExists(user)) {
423: throw new UnknownEntityException("The account '"
424: + user.getName() + "' does not exist");
425: }
426: user.setPassword(TurbineSecurity.encryptPassword(password));
427: // save the changes in the database immediately, to prevent the
428: // password being 'reverted' to the old value if the user data
429: // is lost somehow before it is saved at session's expiry.
430: store(user);
431: }
432:
433: /**
434: * Creates new user account with specified attributes.
435: *
436: * @param user The object describing account to be created.
437: * @param initialPassword the password for the new account
438: * @throws DataBackendException if there was an error accessing
439: the data backend.
440: * @throws EntityExistsException if the user account already exists.
441: */
442: public void createAccount(User user, String initialPassword)
443: throws EntityExistsException, DataBackendException {
444: if (StringUtils.isEmpty(user.getName())) {
445: throw new DataBackendException("Could not create "
446: + "an user with empty name!");
447: }
448:
449: if (accountExists(user)) {
450: throw new EntityExistsException("The account '"
451: + user.getName() + "' already exists");
452: }
453: user.setPassword(TurbineSecurity
454: .encryptPassword(initialPassword));
455:
456: Criteria criteria = TurbineUserPeer.buildCriteria(user);
457: try {
458: // perform the insert to the database
459: ObjectKey pk = TurbineUserPeer.doInsert(criteria);
460:
461: // update the user object with the primary key
462: TurbineMapBuilder mapbuilder = (TurbineMapBuilder) TurbineUserPeer
463: .getMapBuilder("org.apache.turbine.util.db.map.TurbineMapBuilder");
464: user.setPerm(mapbuilder.getUserId(), pk);
465: ((BaseObject) user).setPrimaryKey(pk);
466: } catch (Exception e) {
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) throws UnknownEntityException,
481: DataBackendException {
482: if (!accountExists(user)) {
483: throw new UnknownEntityException("The account '"
484: + user.getName() + "' does not exist");
485: }
486: Criteria criteria = new Criteria();
487: criteria.add(TurbineUserPeer.USERNAME, user.getName());
488: try {
489: TurbineUserPeer.doDelete(criteria);
490: } catch (Exception e) {
491: throw new DataBackendException("Failed to remove account '"
492: + user.getName() + "'", e);
493: }
494: }
495: }
|