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