001: package org.apache.turbine.services.security.torque;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.Iterator;
023: import java.util.List;
024:
025: import org.apache.commons.configuration.Configuration;
026: import org.apache.commons.lang.StringUtils;
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.services.InitializationException;
031: import org.apache.turbine.services.security.TurbineSecurity;
032: import org.apache.turbine.services.security.UserManager;
033: import org.apache.turbine.util.security.DataBackendException;
034: import org.apache.turbine.util.security.EntityExistsException;
035: import org.apache.turbine.util.security.PasswordMismatchException;
036: import org.apache.turbine.util.security.UnknownEntityException;
037:
038: /**
039: * An UserManager performs {@link org.apache.turbine.om.security.User}
040: * objects related tasks on behalf of the
041: * {@link org.apache.turbine.services.security.BaseSecurityService}.
042: *
043: * This implementation uses a relational database for storing user data. It
044: * expects that the User interface implementation will be castable to
045: * {@link org.apache.torque.om.BaseObject}.
046: *
047: * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
048: * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
049: * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
050: * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
051: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
052: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
053: * @version $Id: TorqueUserManager.java 534527 2007-05-02 16:10:59Z tv $
054: */
055: public class TorqueUserManager implements UserManager {
056: /**
057: * Initializes the UserManager
058: *
059: * @param conf A Configuration object to init this Manager
060: *
061: * @throws InitializationException When something went wrong.
062: */
063: public void init(Configuration conf) throws InitializationException {
064: UserPeerManager.init(conf);
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(UserPeerManager.getNameColumn(), userName);
095: List users;
096: try {
097: users = UserPeerManager.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(UserPeerManager.getNameColumn(), userName);
125:
126: List users = retrieveList(criteria);
127: ;
128:
129: if (users.size() > 1) {
130: throw new DataBackendException(
131: "Multiple Users with same username '" + userName
132: + "'");
133: }
134: if (users.size() == 1) {
135: return (User) users.get(0);
136: }
137: throw new UnknownEntityException("Unknown user '" + userName
138: + "'");
139: }
140:
141: /**
142: * Retrieve a user from persistent storage using the primary key
143: *
144: * @param key The primary key object
145: * @return an User object.
146: * @throws UnknownEntityException if the user's record does not
147: * exist in the database.
148: * @throws DataBackendException if there is a problem accessing the
149: * storage.
150: */
151: public User retrieveById(Object key) throws UnknownEntityException,
152: DataBackendException {
153: Criteria criteria = new Criteria();
154: criteria.add(UserPeerManager.getIdColumn(), key);
155:
156: List users = retrieveList(criteria);
157:
158: if (users.size() > 1) {
159: throw new DataBackendException(
160: "Multiple Users with same unique Key '"
161: + String.valueOf(key) + "'");
162: }
163: if (users.size() == 1) {
164: return (User) users.get(0);
165: }
166: throw new UnknownEntityException("Unknown user with key '"
167: + String.valueOf(key) + "'");
168: }
169:
170: /**
171: * @deprecated Use <a href="#retrieveList">retrieveList</a> instead.
172: *
173: * @param criteria The criteria of selection.
174: * @return a List of users meeting the criteria.
175: * @throws DataBackendException if there is a problem accessing the
176: * storage.
177: */
178: public User[] retrieve(Criteria criteria)
179: throws DataBackendException {
180: return (User[]) retrieveList(criteria).toArray(new User[0]);
181: }
182:
183: /**
184: * Retrieve a list of users that meet the specified criteria.
185: *
186: * As the keys for the criteria, you should use the constants that
187: * are defined in {@link User} interface, plus the names
188: * of the custom attributes you added to your user representation
189: * in the data storage. Use verbatim names of the attributes -
190: * without table name prefix in case of Torque implementation.
191: *
192: * @param criteria The criteria of selection.
193: * @return a List of users meeting the criteria.
194: * @throws DataBackendException if there is a problem accessing the
195: * storage.
196: */
197: public List retrieveList(Criteria criteria)
198: throws DataBackendException {
199: for (Iterator keys = criteria.keySet().iterator(); keys
200: .hasNext();) {
201: String key = (String) keys.next();
202:
203: // set the table name for all attached criterion
204: Criteria.Criterion[] criterion = criteria.getCriterion(key)
205: .getAttachedCriterion();
206:
207: for (int i = 0; i < criterion.length; i++) {
208: if (StringUtils.isEmpty(criterion[i].getTable())) {
209: criterion[i].setTable(UserPeerManager
210: .getTableName());
211: }
212: }
213: }
214: List users = null;
215: try {
216: users = UserPeerManager.doSelect(criteria);
217: } catch (Exception e) {
218: throw new DataBackendException("Failed to retrieve users",
219: e);
220: }
221: return users;
222: }
223:
224: /**
225: * Retrieve a user from persistent storage using username as the
226: * key, and authenticate the user. The implementation may chose
227: * to authenticate to the server as the user whose data is being
228: * retrieved.
229: *
230: * @param userName the name of the user.
231: * @param password the user supplied password.
232: * @return an User object.
233: * @exception PasswordMismatchException if the supplied password was
234: * incorrect.
235: * @exception UnknownEntityException if the user's account does not
236: * exist in the database.
237: * @exception DataBackendException if there is a problem accessing the
238: * storage.
239: */
240: public User retrieve(String userName, String password)
241: throws PasswordMismatchException, UnknownEntityException,
242: DataBackendException {
243: User user = retrieve(userName);
244: authenticate(user, password);
245: return user;
246: }
247:
248: /**
249: * Save an User object to persistent storage. User's account is
250: * required to exist in the storage.
251: *
252: * @param user an User object to store.
253: * @exception UnknownEntityException if the user's account does not
254: * exist in the database.
255: * @exception DataBackendException if there is a problem accessing the
256: * storage.
257: */
258: public void store(User user) throws UnknownEntityException,
259: DataBackendException {
260: if (!accountExists(user)) {
261: throw new UnknownEntityException("The account '"
262: + user.getName() + "' does not exist");
263: }
264:
265: try {
266: // this is to mimic the old behavior of the method, the user
267: // should be new that is passed to this method. It would be
268: // better if this was checked, but the original code did not
269: // care about the user's state, so we set it to be appropriate
270: ((Persistent) user).setNew(false);
271: ((Persistent) user).setModified(true);
272: ((Persistent) user).save();
273: } catch (Exception e) {
274: throw new DataBackendException(
275: "Failed to save user object", e);
276: }
277: }
278:
279: /**
280: * Saves User data when the session is unbound. The user account is required
281: * to exist in the storage.
282: *
283: * LastLogin, AccessCounter, persistent pull tools, and any data stored
284: * in the permData hashtable that is not mapped to a column will be saved.
285: *
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 saveOnSessionUnbind(User user)
292: throws UnknownEntityException, DataBackendException {
293: if (!user.hasLoggedIn()) {
294: return;
295: }
296: store(user);
297: }
298:
299: /**
300: * Authenticate an User with the specified password. If authentication
301: * is successful the method returns nothing. If there are any problems,
302: * exception was thrown.
303: *
304: * @param user an User object to authenticate.
305: * @param password the user supplied password.
306: * @exception PasswordMismatchException if the supplied password was
307: * incorrect.
308: * @exception UnknownEntityException if the user's account does not
309: * exist in the database.
310: * @exception DataBackendException if there is a problem accessing the
311: * storage.
312: */
313: public void authenticate(User user, String password)
314: throws PasswordMismatchException, UnknownEntityException,
315: DataBackendException {
316: if (!accountExists(user)) {
317: throw new UnknownEntityException("The account '"
318: + user.getName() + "' does not exist");
319: }
320:
321: // log.debug("Supplied Pass: " + password);
322: // log.debug("User Pass: " + user.getPassword());
323:
324: /*
325: * Unix crypt needs the existing, encrypted password text as
326: * salt for checking the supplied password. So we supply it
327: * into the checkPassword routine
328: */
329:
330: if (!TurbineSecurity
331: .checkPassword(password, user.getPassword())) {
332: throw new PasswordMismatchException(
333: "The passwords do not match");
334: }
335: }
336:
337: /**
338: * Change the password for an User. The user must have supplied the
339: * old password to allow the change.
340: *
341: * @param user an User to change password for.
342: * @param oldPassword The old password to verify
343: * @param newPassword The new password to set
344: * @exception PasswordMismatchException if the supplied password was
345: * incorrect.
346: * @exception UnknownEntityException if the user's account does not
347: * exist in the database.
348: * @exception DataBackendException if there is a problem accessing the
349: * storage.
350: */
351: public void changePassword(User user, String oldPassword,
352: String newPassword) throws PasswordMismatchException,
353: UnknownEntityException, DataBackendException {
354: if (!accountExists(user)) {
355: throw new UnknownEntityException("The account '"
356: + user.getName() + "' does not exist");
357: }
358:
359: if (!TurbineSecurity.checkPassword(oldPassword, user
360: .getPassword())) {
361: throw new PasswordMismatchException(
362: "The supplied old password for '" + user.getName()
363: + "' was incorrect");
364: }
365: user.setPassword(TurbineSecurity.encryptPassword(newPassword));
366: // save the changes in the database imediately, to prevent the password
367: // being 'reverted' to the old value if the user data is lost somehow
368: // before it is saved at session's expiry.
369: store(user);
370: }
371:
372: /**
373: * Forcibly sets new password for an User.
374: *
375: * This is supposed by the administrator to change the forgotten or
376: * compromised passwords. Certain implementatations of this feature
377: * would require administrative level access to the authenticating
378: * server / program.
379: *
380: * @param user an User to change password for.
381: * @param password the new password.
382: * @exception UnknownEntityException if the user's record does not
383: * exist in the database.
384: * @exception DataBackendException if there is a problem accessing the
385: * storage.
386: */
387: public void forcePassword(User user, String password)
388: throws UnknownEntityException, DataBackendException {
389: if (!accountExists(user)) {
390: throw new UnknownEntityException("The account '"
391: + user.getName() + "' does not exist");
392: }
393: user.setPassword(TurbineSecurity.encryptPassword(password));
394: // save the changes in the database immediately, to prevent the
395: // password being 'reverted' to the old value if the user data
396: // is lost somehow before it is saved at session's expiry.
397: store(user);
398: }
399:
400: /**
401: * Creates new user account with specified attributes.
402: *
403: * @param user The object describing account to be created.
404: * @param initialPassword the password for the new account
405: * @throws DataBackendException if there was an error accessing
406: the data backend.
407: * @throws EntityExistsException if the user account already exists.
408: */
409: public void createAccount(User user, String initialPassword)
410: throws EntityExistsException, DataBackendException {
411: if (StringUtils.isEmpty(user.getName())) {
412: throw new DataBackendException("Could not create "
413: + "an user with empty name!");
414: }
415:
416: if (accountExists(user)) {
417: throw new EntityExistsException("The account '"
418: + user.getName() + "' already exists");
419: }
420: user.setPassword(TurbineSecurity
421: .encryptPassword(initialPassword));
422:
423: try {
424: // this is to mimic the old behavior of the method, the user
425: // should be new that is passed to this method. It would be
426: // better if this was checked, but the original code did not
427: // care about the user's state, so we set it to be appropriate
428: ((Persistent) user).setNew(true);
429: ((Persistent) user).setModified(true);
430: ((Persistent) user).save();
431: } catch (Exception e) {
432: throw new DataBackendException("Failed to create account '"
433: + user.getName() + "'", e);
434: }
435: }
436:
437: /**
438: * Removes an user account from the system.
439: *
440: * @param user the object describing the account to be removed.
441: * @throws DataBackendException if there was an error accessing
442: the data backend.
443: * @throws UnknownEntityException if the user account is not present.
444: */
445: public void removeAccount(User user) throws UnknownEntityException,
446: DataBackendException {
447: if (!accountExists(user)) {
448: throw new UnknownEntityException("The account '"
449: + user.getName() + "' does not exist");
450: }
451: Criteria criteria = new Criteria();
452: criteria.add(UserPeerManager.getNameColumn(), user.getName());
453: try {
454: UserPeerManager.doDelete(criteria);
455: } catch (Exception e) {
456: throw new DataBackendException("Failed to remove account '"
457: + user.getName() + "'", e);
458: }
459: }
460: }
|