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 1096130 2011-04-23 10:37:19Z ludwig $ 054 */ 055 public class TorqueUserManager 056 implements UserManager 057 { 058 /** 059 * Initializes the UserManager 060 * 061 * @param conf A Configuration object to init this Manager 062 * 063 * @throws InitializationException When something went wrong. 064 */ 065 public void init(Configuration conf) 066 throws InitializationException 067 { 068 UserPeerManager.init(conf); 069 } 070 071 /** 072 * Check whether a specified user's account exists. 073 * 074 * The login name is used for looking up the account. 075 * 076 * @param user The user to be checked. 077 * @return true if the specified account exists 078 * @throws DataBackendException if there was an error accessing 079 * the data backend. 080 */ 081 public boolean accountExists(User user) 082 throws DataBackendException 083 { 084 return accountExists(user.getName()); 085 } 086 087 /** 088 * Check whether a specified user's account exists. 089 * 090 * The login name is used for looking up the account. 091 * 092 * @param userName The name of the user to be checked. 093 * @return true if the specified account exists 094 * @throws DataBackendException if there was an error accessing 095 * the data backend. 096 */ 097 public boolean accountExists(String userName) 098 throws DataBackendException 099 { 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 }