View Javadoc
1 package org.apache.turbine.services.security; 2 3 /* ==================================================================== 4 * The Apache Software License, Version 1.1 5 * 6 * Copyright (c) 2001 The Apache Software Foundation. All rights 7 * reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in 18 * the documentation and/or other materials provided with the 19 * distribution. 20 * 21 * 3. The end-user documentation included with the redistribution, 22 * if any, must include the following acknowledgment: 23 * "This product includes software developed by the 24 * Apache Software Foundation (http://www.apache.org/)." 25 * Alternately, this acknowledgment may appear in the software itself, 26 * if and wherever such third-party acknowledgments normally appear. 27 * 28 * 4. The names "Apache" and "Apache Software Foundation" and 29 * "Apache Turbine" must not be used to endorse or promote products 30 * derived from this software without prior written permission. For 31 * written permission, please contact apache@apache.org. 32 * 33 * 5. Products derived from this software may not be called "Apache", 34 * "Apache Turbine", nor may "Apache" appear in their name, without 35 * prior written permission of the Apache Software Foundation. 36 * 37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 48 * SUCH DAMAGE. 49 * ==================================================================== 50 * 51 * This software consists of voluntary contributions made by many 52 * individuals on behalf of the Apache Software Foundation. For more 53 * information on the Apache Software Foundation, please see 54 * <http://www.apache.org/>;. 55 */ 56 57 import java.io.ByteArrayOutputStream; 58 import java.io.OutputStream; 59 import java.security.MessageDigest; 60 import javax.mail.internet.MimeUtility; 61 import javax.servlet.ServletConfig; 62 import org.apache.torque.util.Criteria; 63 import org.apache.turbine.om.security.Group; 64 import org.apache.turbine.om.security.Permission; 65 import org.apache.turbine.om.security.Role; 66 import org.apache.turbine.om.security.User; 67 import org.apache.turbine.services.InitializationException; 68 import org.apache.turbine.services.TurbineBaseService; 69 import org.apache.turbine.util.Log; 70 import org.apache.turbine.util.security.DataBackendException; 71 import org.apache.turbine.util.security.EntityExistsException; 72 import org.apache.turbine.util.security.GroupSet; 73 import org.apache.turbine.util.security.PasswordMismatchException; 74 import org.apache.turbine.util.security.PermissionSet; 75 import org.apache.turbine.util.security.RoleSet; 76 import org.apache.turbine.util.security.UnknownEntityException; 77 78 /*** 79 * This is a common subset of SecurityService implementation. 80 * 81 * Provided functionality includes: 82 * <ul> 83 * <li> methods for retrieving User objects, that delegates functionality 84 * to the pluggable implementations of the User interface. 85 * <li> synchronization mechanism for methods reading/modifying the security 86 * information, that guarantees that multiple threads may read the 87 * information concurrently, but threads that modify the information 88 * acquires exclusive access. 89 * <li> implementation of convenience methods for retrieving security entities 90 * that maintain in-memory caching of objects for fast access. 91 * </ul> 92 * 93 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a> 94 * @version $Id: BaseSecurityService.java,v 1.4 2002/07/11 16:53:24 mpoeschl Exp $ 95 */ 96 public abstract class BaseSecurityService 97 extends TurbineBaseService implements SecurityService 98 { 99 // management of Groups/Role/Permissions 100 101 /*** Holds a list of all groups in the systems, for speeding up the access */ 102 private GroupSet allGroups = null; 103 104 /*** Holds a list of all roles in the systems, for speeding up the access */ 105 private RoleSet allRoles = null; 106 107 /*** Holds a list of all permissions in the systems, for speeding up the access */ 108 private PermissionSet allPermissions = null; 109 110 /*** The number of threads concurrently reading security information */ 111 private int readerCount = 0; 112 113 /*** The instance of UserManager the SecurityService uses */ 114 private UserManager userManager = null; 115 116 /*** The class of User the SecurityService uses */ 117 private Class userClass = null; 118 119 /*** 120 * The Group object that represents the <a href="#global">global group</a>. 121 */ 122 private static Group globalGroup = null; 123 124 /*** 125 * This method provides client-side encryption of passwords. 126 * 127 * If <code>secure.passwords</code> are enabled in TurbineResources, 128 * the password will be encrypted, if not, it will be returned unchanged. 129 * The <code>secure.passwords.algorithm</code> property can be used 130 * to chose which digest algorithm should be used for performing the 131 * encryption. <code>SHA</code> is used by default. 132 * 133 * @param password the password to process 134 * @return processed password 135 */ 136 public String encryptPassword( String password ) 137 { 138 if(password == null) 139 return null; 140 String secure = getProperties().getProperty( 141 SecurityService.SECURE_PASSWORDS_KEY, 142 SecurityService.SECURE_PASSWORDS_DEFAULT).toLowerCase(); 143 String algorithm = getProperties().getProperty( 144 SecurityService.SECURE_PASSWORDS_ALGORITHM_KEY, 145 SecurityService.SECURE_PASSWORDS_ALGORITHM_DEFAULT); 146 if (secure.equals("true") || secure.equals("yes")) 147 { 148 try 149 { 150 MessageDigest md = MessageDigest.getInstance(algorithm); 151 // We need to use unicode here, to be independent of platform's 152 // default encoding. Thanks to SGawin for spotting this. 153 byte[] digest = md.digest(password.getBytes("UTF-8")); 154 ByteArrayOutputStream bas = new ByteArrayOutputStream(digest.length + digest.length / 3 + 1); 155 OutputStream encodedStream = MimeUtility.encode(bas, "base64"); 156 encodedStream.write(digest); 157 return bas.toString(); 158 } 159 catch (Exception e) 160 { 161 Log.error("Unable to encrypt password."+e.getMessage()); 162 Log.error(e); 163 164 return null; 165 } 166 } else { 167 return password; 168 } 169 } 170 171 /*** 172 * Initializes the SecurityService, locating the apropriate UserManager 173 * 174 * @param config a ServletConfig, to enforce early initialization 175 */ 176 public void init(ServletConfig config) 177 throws InitializationException 178 { 179 String userManagerClassName = getProperties().getProperty( 180 SecurityService.USER_MANAGER_KEY, 181 SecurityService.USER_MANAGER_DEFAULT); 182 183 String userClassName = getProperties().getProperty( 184 SecurityService.USER_CLASS_KEY, 185 SecurityService.USER_CLASS_DEFAULT); 186 187 try 188 { 189 userClass = Class.forName(userClassName); 190 } 191 catch(Exception e) 192 { 193 throw new InitializationException( 194 "BaseSecurityService.init: Failed create a Class object for User implementation", e); 195 } 196 197 try 198 { 199 userManager = (UserManager)Class. 200 forName(userManagerClassName).newInstance(); 201 setInit(true); 202 } 203 catch(Exception e) 204 { 205 throw new InitializationException( 206 "BaseSecurityService.init: Failed to instantiate UserManager" ,e); 207 } 208 209 } 210 211 212 213 /*** 214 * Return a Class object representing the system's chosen implementation of 215 * of User interface. 216 * 217 * @return systems's chosen implementation of User interface. 218 * @throws UnknownEntityException if the implementation of User interface 219 * could not be determined, or does not exist. 220 */ 221 public Class getUserClass() 222 throws UnknownEntityException 223 { 224 if ( userClass == null ) 225 { 226 throw new UnknownEntityException( 227 "Failed to create a Class object for User implementation"); 228 } 229 return userClass; 230 } 231 232 /*** 233 * Construct a blank User object. 234 * 235 * This method calls getUserClass, and then creates a new object using 236 * the default constructor. 237 * 238 * @return an object implementing User interface. 239 * @throws UnknownEntityException if the object could not be instantiated. 240 */ 241 public User getUserInstance() 242 throws UnknownEntityException 243 { 244 User user; 245 try 246 { 247 user = (User)getUserClass().newInstance(); 248 } 249 catch(Exception e) 250 { 251 throw new UnknownEntityException("Failed instantiate an User implementation object", e); 252 } 253 return user; 254 } 255 256 /*** 257 * Check whether a specified user's account exists. 258 * 259 * The login name is used for looking up the account. 260 * 261 * @param user The user to be checked. 262 * @return true if the specified account exists 263 * @throws DataBackendException if there was an error accessing the data backend. 264 */ 265 public boolean accountExists( User user ) 266 throws DataBackendException 267 { 268 return userManager.accountExists(user); 269 } 270 271 /*** 272 * Check whether a specified user's account exists. 273 * 274 * The login name is used for looking up the account. 275 * 276 * @param usename The name of the user to be checked. 277 * @return true if the specified account exists 278 * @throws DataBackendException if there was an error accessing the data backend. 279 */ 280 public boolean accountExists( String username ) 281 throws DataBackendException 282 { 283 return userManager.accountExists(username); 284 } 285 286 /*** 287 * Authenticates an user, and constructs an User object to represent 288 * him/her. 289 * 290 * @param username The user name. 291 * @param password The user password. 292 * @return An authenticated Turbine User. 293 * @exception PasswordMismatchException if the supplied password was 294 * incorrect. 295 * @exception UnknownEntityException if the user's account does not 296 * exist in the database. 297 * @exception DataBackendException if there is a problem accessing the 298 * storage. 299 */ 300 public User getAuthenticatedUser( String username, String password ) 301 throws DataBackendException, UnknownEntityException, 302 PasswordMismatchException 303 { 304 return userManager.retrieve(username, password); 305 } 306 307 /*** 308 * Constructs an User object to represent a registered user of the application. 309 * 310 * @param username The user name. 311 * @return A Turbine User. 312 * @exception UnknownEntityException if the user's account does not 313 * exist in the database. 314 * @exception DataBackendException if there is a problem accessing the 315 * storage. 316 */ 317 public User getUser( String username ) 318 throws DataBackendException, UnknownEntityException 319 { 320 return userManager.retrieve(username); 321 } 322 323 /*** 324 * Retrieve a set of users that meet the specified criteria. 325 * 326 * As the keys for the criteria, you should use the constants that 327 * are defined in {@link User} interface, plus the names 328 * of the custom attributes you added to your user representation 329 * in the data storage. Use verbatim names of the attributes - 330 * without table name prefix in case of DB implementation. 331 * 332 * @param criteria The criteria of selection. 333 * @return a List of users meeting the criteria. 334 * @throws DataBackendException if there is a problem accessing the 335 * storage. 336 */ 337 public User[] getUsers( Criteria criteria ) 338 throws DataBackendException 339 { 340 return userManager.retrieve(criteria); 341 } 342 343 /*** 344 * Constructs an User object to represent an anonymous user of the application. 345 * 346 * @return An anonymous Turbine User. 347 * @throws UnknownEntityException if the implementation of User interface 348 * could not be determined, or does not exist. 349 */ 350 public User getAnonymousUser() 351 throws UnknownEntityException 352 { 353 User user = getUserInstance(); 354 user.setUserName(""); 355 return user; 356 } 357 358 /*** 359 * Saves User's data in the permanent storage. The user account is required 360 * to exist in the storage. 361 * 362 * @exception UnknownEntityException if the user's account does not 363 * exist in the database. 364 * @exception DataBackendException if there is a problem accessing the 365 * storage. 366 */ 367 public void saveUser( User user ) 368 throws UnknownEntityException, DataBackendException 369 { 370 userManager.store(user); 371 } 372 373 /*** 374 * Creates new user account with specified attributes. 375 * 376 * @param user the object describing account to be created. 377 * @throws DataBackendException if there was an error accessing the data backend. 378 * @throws EntityExistsException if the user account already exists. 379 */ 380 public void addUser( User user, String password ) 381 throws DataBackendException, EntityExistsException 382 { 383 userManager.createAccount(user, password); 384 } 385 386 /*** 387 * Removes an user account from the system. 388 * 389 * @param user the object describing the account to be removed. 390 * @throws DataBackendException if there was an error accessing the data backend. 391 * @throws UnknownEntityException if the user account is not present. 392 */ 393 public void removeUser( User user ) 394 throws DataBackendException, UnknownEntityException 395 { 396 // revoke all roles form the user 397 revokeAll(user); 398 399 userManager.removeAccount(user); 400 } 401 402 /*** 403 * Change the password for an User. 404 * 405 * @param user an User to change password for. 406 * @param oldPassword the current password supplied by the user. 407 * @param newPassword the current password requested by the user. 408 * @exception PasswordMismatchException if the supplied password was 409 * incorrect. 410 * @exception UnknownEntityException if the user's record does not 411 * exist in the database. 412 * @exception DataBackendException if there is a problem accessing the 413 * storage. 414 */ 415 public void changePassword( User user, String oldPassword, String newPassword ) 416 throws PasswordMismatchException, UnknownEntityException, 417 DataBackendException 418 { 419 userManager.changePassword(user, oldPassword, newPassword); 420 } 421 422 /*** 423 * Forcibly sets new password for an User. 424 * 425 * This is supposed by the administrator to change the forgotten or 426 * compromised passwords. Certain implementatations of this feature 427 * would require administrative level access to the authenticating 428 * server / program. 429 * 430 * @param user an User to change password for. 431 * @param password the new password. 432 * @exception UnknownEntityException if the user's record does not 433 * exist in the database. 434 * @exception DataBackendException if there is a problem accessing the 435 * storage. 436 */ 437 public void forcePassword( User user, String password ) 438 throws UnknownEntityException, DataBackendException 439 { 440 userManager.forcePassword( user, password ); 441 } 442 443 /*** 444 * Acquire a shared lock on the security information repository. 445 * 446 * Methods that read security information need to invoke this 447 * method at the beginning of their body. 448 */ 449 protected synchronized void lockShared() 450 { 451 readerCount++; 452 } 453 454 /*** 455 * Release a shared lock on the security information repository. 456 * 457 * Methods that read security information need to invoke this 458 * method at the end of their body. 459 */ 460 protected synchronized void unlockShared() 461 { 462 readerCount--; 463 this.notify(); 464 } 465 466 /*** 467 * Acquire an exclusive lock on the security information repository. 468 * 469 * Methods that modify security information need to invoke this 470 * method at the beginning of their body. Note! Those methods must 471 * be <code>synchronized</code> themselves! 472 */ 473 protected void lockExclusive() 474 { 475 while(readerCount>0) 476 { 477 try 478 { 479 this.wait(); 480 } 481 catch(InterruptedException e) 482 { 483 } 484 } 485 } 486 487 /*** 488 * Release an exclusive lock on the security information repository. 489 * 490 * This method is provided only for completeness. It does not really 491 * do anything. Note! Methods that modify security information 492 * must be <code>synchronized</code>! 493 */ 494 protected void unlockExclusive() 495 { 496 // do nothing 497 } 498 499 /*** 500 * Provides a reference to the Group object that represents the 501 * <a href="#global">global group</a>. 502 * 503 * @return a Group object that represents the global group. 504 */ 505 public Group getGlobalGroup() 506 { 507 if(globalGroup == null) 508 { 509 synchronized(BaseSecurityService.class) 510 { 511 if(globalGroup == null) 512 { 513 try 514 { 515 globalGroup = getAllGroups() 516 .getGroup(Group.GLOBAL_GROUP_NAME); 517 } 518 catch (DataBackendException e) 519 { 520 Log.error("Failed to retrieve global group object"); 521 Log.error(e); 522 } 523 } 524 } 525 } 526 return globalGroup; 527 } 528 529 /*** 530 * Retrieve a Group object with specified name. 531 * 532 * @param name the name of the Group. 533 * @return an object representing the Group with specified name. 534 */ 535 public Group getGroup( String name ) 536 throws DataBackendException, UnknownEntityException 537 { 538 GroupSet groups = getAllGroups(); 539 Group group = groups.getGroup(name); 540 if(group != null) 541 { 542 return group; 543 } 544 else 545 { 546 throw new UnknownEntityException("The specified group does not exist"); 547 } 548 } 549 550 /*** 551 * Retrieve a Role object with specified name. 552 * 553 * @param name the name of the Role. 554 * @return an object representing the Role with specified name. 555 */ 556 public Role getRole( String name ) 557 throws DataBackendException, UnknownEntityException 558 { 559 RoleSet roles = getAllRoles(); 560 Role role = roles.getRole(name); 561 if(role != null) 562 { 563 role.setPermissions(getPermissions(role)); 564 return role; 565 } 566 else 567 { 568 throw new UnknownEntityException("The specified role does not exist"); 569 } 570 } 571 572 /*** 573 * Retrieve a Permission object with specified name. 574 * 575 * @param name the name of the Permission. 576 * @return an object representing the Permission with specified name. 577 */ 578 public Permission getPermission( String name ) 579 throws DataBackendException, UnknownEntityException 580 { 581 PermissionSet permissions = getAllPermissions(); 582 Permission permission = permissions.getPermission(name); 583 if(permission != null) 584 { 585 return permission; 586 } 587 else 588 { 589 throw new UnknownEntityException("The specified permission does not exist"); 590 } 591 } 592 593 /*** 594 * Retrieves all groups defined in the system. 595 * 596 * @return the names of all groups defined in the system. 597 * @throws DataBackendException if there was an error accessing the data backend. 598 */ 599 public GroupSet getAllGroups() 600 throws DataBackendException 601 { 602 if(allGroups == null) 603 { 604 synchronized(this) 605 { 606 if(allGroups == null) 607 { 608 allGroups = getGroups( new Criteria() ); 609 } 610 } 611 } 612 return allGroups; 613 } 614 615 /*** 616 * Retrieves all roles defined in the system. 617 * 618 * @return the names of all roles defined in the system. 619 * @throws DataBackendException if there was an error accessing the data backend. 620 */ 621 public RoleSet getAllRoles() 622 throws DataBackendException 623 { 624 if(allRoles == null) 625 { 626 synchronized(this) 627 { 628 if(allRoles == null) 629 { 630 allRoles = getRoles( new Criteria() ); 631 } 632 } 633 } 634 return allRoles; 635 } 636 637 /*** 638 * Retrieves all permissions defined in the system. 639 * 640 * @return the names of all roles defined in the system. 641 * @throws DataBackendException if there was an error accessing the data backend. 642 */ 643 public PermissionSet getAllPermissions() 644 throws DataBackendException 645 { 646 if(allPermissions == null) 647 { 648 synchronized(this) 649 { 650 if(allPermissions == null) 651 { 652 allPermissions = getPermissions( new Criteria() ); 653 } 654 } 655 } 656 return allPermissions; 657 } 658 }

This page was automatically generated by Maven