View Javadoc

1   package org.apache.turbine.services.security;
2   
3   
4   /*
5    * Licensed to the Apache Software Foundation (ASF) under one
6    * or more contributor license agreements.  See the NOTICE file
7    * distributed with this work for additional information
8    * regarding copyright ownership.  The ASF licenses this file
9    * to you under the Apache License, Version 2.0 (the
10   * "License"); you may not use this file except in compliance
11   * with the License.  You may obtain a copy of the License at
12   *
13   *   http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing,
16   * software distributed under the License is distributed on an
17   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   * KIND, either express or implied.  See the License for the
19   * specific language governing permissions and limitations
20   * under the License.
21   */
22  
23  
24  import java.util.Map;
25  
26  import javax.servlet.ServletConfig;
27  
28  import org.apache.commons.configuration.Configuration;
29  import org.apache.commons.lang.StringUtils;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.fulcrum.crypto.CryptoAlgorithm;
33  import org.apache.fulcrum.crypto.CryptoService;
34  import org.apache.fulcrum.factory.FactoryService;
35  import org.apache.turbine.om.security.Group;
36  import org.apache.turbine.om.security.Permission;
37  import org.apache.turbine.om.security.Role;
38  import org.apache.turbine.om.security.User;
39  import org.apache.turbine.services.InitializationException;
40  import org.apache.turbine.services.ServiceManager;
41  import org.apache.turbine.services.TurbineBaseService;
42  import org.apache.turbine.services.TurbineServices;
43  import org.apache.turbine.util.security.AccessControlList;
44  import org.apache.turbine.util.security.DataBackendException;
45  import org.apache.turbine.util.security.EntityExistsException;
46  import org.apache.turbine.util.security.GroupSet;
47  import org.apache.turbine.util.security.PasswordMismatchException;
48  import org.apache.turbine.util.security.PermissionSet;
49  import org.apache.turbine.util.security.RoleSet;
50  import org.apache.turbine.util.security.UnknownEntityException;
51  
52  /**
53   * This is a common subset of SecurityService implementation.
54   *
55   * Provided functionality includes:
56   * <ul>
57   * <li> methods for retrieving User objects, that delegates functionality
58   *      to the pluggable implementations of the User interface.
59   * <li> synchronization mechanism for methods reading/modifying the security
60   *      information, that guarantees that multiple threads may read the
61   *      information concurrently, but threads that modify the information
62   *      acquires exclusive access.
63   * <li> implementation of convenience methods for retrieving security entities
64   *      that maintain in-memory caching of objects for fast access.
65   * </ul>
66   *
67   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
68   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
69   * @author <a href="mailto:marco@intermeta.de">Marco Kn&uuml;ttel</a>
70   * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
71   * @version $Id: BaseSecurityService.java 1096130 2011-04-23 10:37:19Z ludwig $
72   */
73  public abstract class BaseSecurityService
74          extends TurbineBaseService
75          implements SecurityService
76  {
77      /** The number of threads concurrently reading security information */
78      private int readerCount = 0;
79  
80      /** The instance of UserManager the SecurityService uses */
81      private UserManager userManager = null;
82  
83      /** The class of User the SecurityService uses */
84      private Class userClass = null;
85  
86      /** The class of Group the SecurityService uses */
87      private Class groupClass = null;
88  
89      /** The class of Permission the SecurityService uses */
90      private Class permissionClass = null;
91  
92      /** The class of Role the SecurityService uses */
93      private Class roleClass = null;
94  
95      /** The class of ACL the SecurityService uses */
96      private Class aclClass = null;
97  
98      /** A factory to construct ACL Objects */
99      private FactoryService aclFactoryService = null;
100 
101     /**
102      * The Group object that represents the <a href="#global">global group</a>.
103      */
104     private static Group globalGroup = null;
105 
106     /** Logging */
107     private static Log log = LogFactory.getLog(BaseSecurityService.class);
108 
109     /**
110      * This method provides client-side encryption of passwords.
111      *
112      * If <code>secure.passwords</code> are enabled in TurbineResources,
113      * the password will be encrypted, if not, it will be returned unchanged.
114      * The <code>secure.passwords.algorithm</code> property can be used
115      * to chose which digest algorithm should be used for performing the
116      * encryption. <code>SHA</code> is used by default.
117      *
118      * @param password the password to process
119      * @return processed password
120      */
121     public String encryptPassword(String password)
122     {
123         return encryptPassword(password, null);
124     }
125 
126     /**
127      * This method provides client-side encryption of passwords.
128      *
129      * If <code>secure.passwords</code> are enabled in TurbineResources,
130      * the password will be encrypted, if not, it will be returned unchanged.
131      * The <code>secure.passwords.algorithm</code> property can be used
132      * to chose which digest algorithm should be used for performing the
133      * encryption. <code>SHA</code> is used by default.
134      *
135      * The used algorithms must be prepared to accept null as a
136      * valid parameter for salt. All algorithms in the Fulcrum Cryptoservice
137      * accept this.
138      *
139      * @param password the password to process
140      * @param salt     algorithms that needs a salt can provide one here
141      * @return processed password
142      */
143 
144     public String encryptPassword(String password, String salt)
145     {
146         if (password == null)
147         {
148             return null;
149         }
150         String secure = getConfiguration().getString(
151                 SecurityService.SECURE_PASSWORDS_KEY,
152                 SecurityService.SECURE_PASSWORDS_DEFAULT).toLowerCase();
153 
154         String algorithm = getConfiguration().getString(
155                 SecurityService.SECURE_PASSWORDS_ALGORITHM_KEY,
156                 SecurityService.SECURE_PASSWORDS_ALGORITHM_DEFAULT);
157 
158         CryptoService cs = null;
159         try {
160             ServiceManager serviceManager = TurbineServices.getInstance();
161             cs = (CryptoService)serviceManager.getService(CryptoService.ROLE);
162         }
163         catch (Exception e){
164             throw new RuntimeException("Could not access Crypto Service",e);
165         }
166 
167         if (cs != null && (secure.equals("true") || secure.equals("yes")))
168         {
169             try
170             {
171                 CryptoAlgorithm ca = cs.getCryptoAlgorithm(algorithm);
172 
173                 ca.setSeed(salt);
174 
175                 String result = ca.encrypt(password);
176 
177                 return result;
178             }
179             catch (Exception e)
180             {
181                 log.error("Unable to encrypt password: ", e);
182 
183                 return null;
184             }
185         }
186         else
187         {
188             return password;
189         }
190     }
191 
192     /**
193      * Checks if a supplied password matches the encrypted password
194      *
195      * @param checkpw      The clear text password supplied by the user
196      * @param encpw        The current, encrypted password
197      *
198      * @return true if the password matches, else false
199      *
200      */
201 
202     public boolean checkPassword(String checkpw, String encpw)
203     {
204         String result = encryptPassword(checkpw, encpw);
205 
206         return (result == null) ? false : result.equals(encpw);
207     }
208 
209     /**
210      * Initializes the SecurityService, locating the apropriate UserManager
211      * This is a zero parameter variant which queries the Turbine Servlet
212      * for its config.
213      *
214      * @throws InitializationException Something went wrong in the init stage
215      */
216     public void init()
217             throws InitializationException
218     {
219         Configuration conf = getConfiguration();
220 
221         String userManagerClassName = conf.getString(
222                 SecurityService.USER_MANAGER_KEY,
223                 SecurityService.USER_MANAGER_DEFAULT);
224 
225         String userClassName = conf.getString(
226                 SecurityService.USER_CLASS_KEY,
227                 SecurityService.USER_CLASS_DEFAULT);
228 
229         String groupClassName = conf.getString(
230                 SecurityService.GROUP_CLASS_KEY,
231                 SecurityService.GROUP_CLASS_DEFAULT);
232 
233         String permissionClassName = conf.getString(
234                 SecurityService.PERMISSION_CLASS_KEY,
235                 SecurityService.PERMISSION_CLASS_DEFAULT);
236 
237         String roleClassName = conf.getString(
238                 SecurityService.ROLE_CLASS_KEY,
239                 SecurityService.ROLE_CLASS_DEFAULT);
240 
241         String aclClassName = conf.getString(
242                 SecurityService.ACL_CLASS_KEY,
243                 SecurityService.ACL_CLASS_DEFAULT);
244 
245         try
246         {
247             userClass = Class.forName(userClassName);
248             groupClass = Class.forName(groupClassName);
249             permissionClass = Class.forName(permissionClassName);
250             roleClass = Class.forName(roleClassName);
251             aclClass = Class.forName(aclClassName);
252         }
253         catch (Exception e)
254         {
255             if (userClass == null)
256             {
257                 throw new InitializationException(
258                         "Failed to create a Class object for User implementation", e);
259             }
260             if (groupClass == null)
261             {
262                 throw new InitializationException(
263                         "Failed to create a Class object for Group implementation", e);
264             }
265             if (permissionClass == null)
266             {
267                 throw new InitializationException(
268                         "Failed to create a Class object for Permission implementation", e);
269             }
270             if (roleClass == null)
271             {
272                 throw new InitializationException(
273                         "Failed to create a Class object for Role implementation", e);
274             }
275             if (aclClass == null)
276             {
277                 throw new InitializationException(
278                         "Failed to create a Class object for ACL implementation", e);
279             }
280         }
281 
282         try
283         {
284             UserManager userManager =
285                     (UserManager) Class.forName(userManagerClassName).newInstance();
286 
287             userManager.init(conf);
288 
289             setUserManager(userManager);
290         }
291         catch (Exception e)
292         {
293             throw new InitializationException("Failed to instantiate UserManager", e);
294         }
295 
296         try
297         {
298             aclFactoryService = (FactoryService)TurbineServices.getInstance().getService(FactoryService.ROLE);
299         }
300         catch (Exception e)
301         {
302             throw new InitializationException(
303                     "BaseSecurityService.init: Failed to get the Factory Service object", e);
304         }
305 
306         setInit(true);
307     }
308 
309     /**
310      * Return a Class object representing the system's chosen implementation of
311      * of User interface.
312      *
313      * @return systems's chosen implementation of User interface.
314      * @throws UnknownEntityException if the implementation of User interface
315      *         could not be determined, or does not exist.
316      */
317     public Class getUserClass()
318             throws UnknownEntityException
319     {
320         if (userClass == null)
321         {
322             throw new UnknownEntityException(
323                     "Failed to create a Class object for User implementation");
324         }
325         return userClass;
326     }
327 
328     /**
329      * Construct a blank User object.
330      *
331      * This method calls getUserClass, and then creates a new object using
332      * the default constructor.
333      *
334      * @return an object implementing User interface.
335      * @throws UnknownEntityException if the object could not be instantiated.
336      */
337     public User getUserInstance()
338             throws UnknownEntityException
339     {
340         User user;
341         try
342         {
343             user = (User) getUserClass().newInstance();
344         }
345         catch (Exception e)
346         {
347             throw new UnknownEntityException(
348                     "Failed instantiate an User implementation object", e);
349         }
350         return user;
351     }
352 
353     /**
354      * Construct a blank User object.
355      *
356      * This method calls getUserClass, and then creates a new object using
357      * the default constructor.
358      *
359      * @param userName The name of the user.
360      *
361      * @return an object implementing User interface.
362      *
363      * @throws UnknownEntityException if the object could not be instantiated.
364      */
365     public User getUserInstance(String userName)
366             throws UnknownEntityException
367     {
368         User user = getUserInstance();
369         user.setName(userName);
370         return user;
371     }
372 
373     /**
374      * Return a Class object representing the system's chosen implementation of
375      * of Group interface.
376      *
377      * @return systems's chosen implementation of Group interface.
378      * @throws UnknownEntityException if the implementation of Group interface
379      *         could not be determined, or does not exist.
380      */
381     public Class getGroupClass()
382             throws UnknownEntityException
383     {
384         if (groupClass == null)
385         {
386             throw new UnknownEntityException(
387                     "Failed to create a Class object for Group implementation");
388         }
389         return groupClass;
390     }
391 
392     /**
393      * Construct a blank Group object.
394      *
395      * This method calls getGroupClass, and then creates a new object using
396      * the default constructor.
397      *
398      * @return an object implementing Group interface.
399      * @throws UnknownEntityException if the object could not be instantiated.
400      */
401     public Group getGroupInstance()
402             throws UnknownEntityException
403     {
404         Group group;
405         try
406         {
407             group = (Group) getGroupClass().newInstance();
408         }
409         catch (Exception e)
410         {
411             throw new UnknownEntityException("Failed to instantiate a Group implementation object", e);
412         }
413         return group;
414     }
415 
416     /**
417      * Construct a blank Group object.
418      *
419      * This method calls getGroupClass, and then creates a new object using
420      * the default constructor.
421      *
422      * @param groupName The name of the Group
423      *
424      * @return an object implementing Group interface.
425      *
426      * @throws UnknownEntityException if the object could not be instantiated.
427      */
428     public Group getGroupInstance(String groupName)
429             throws UnknownEntityException
430     {
431         Group group = getGroupInstance();
432         group.setName(groupName);
433         return group;
434     }
435 
436     /**
437      * Return a Class object representing the system's chosen implementation of
438      * of Permission interface.
439      *
440      * @return systems's chosen implementation of Permission interface.
441      * @throws UnknownEntityException if the implementation of Permission interface
442      *         could not be determined, or does not exist.
443      */
444     public Class getPermissionClass()
445             throws UnknownEntityException
446     {
447         if (permissionClass == null)
448         {
449             throw new UnknownEntityException(
450                     "Failed to create a Class object for Permission implementation");
451         }
452         return permissionClass;
453     }
454 
455     /**
456      * Construct a blank Permission object.
457      *
458      * This method calls getPermissionClass, and then creates a new object using
459      * the default constructor.
460      *
461      * @return an object implementing Permission interface.
462      * @throws UnknownEntityException if the object could not be instantiated.
463      */
464     public Permission getPermissionInstance()
465             throws UnknownEntityException
466     {
467         Permission permission;
468         try
469         {
470             permission = (Permission) getPermissionClass().newInstance();
471         }
472         catch (Exception e)
473         {
474             throw new UnknownEntityException("Failed to instantiate a Permission implementation object", e);
475         }
476         return permission;
477     }
478 
479     /**
480      * Construct a blank Permission object.
481      *
482      * This method calls getPermissionClass, and then creates a new object using
483      * the default constructor.
484      *
485      * @param permName The name of the permission.
486      *
487      * @return an object implementing Permission interface.
488      * @throws UnknownEntityException if the object could not be instantiated.
489      */
490     public Permission getPermissionInstance(String permName)
491             throws UnknownEntityException
492     {
493         Permission perm = getPermissionInstance();
494         perm.setName(permName);
495         return perm;
496     }
497 
498     /**
499      * Return a Class object representing the system's chosen implementation of
500      * of Role interface.
501      *
502      * @return systems's chosen implementation of Role interface.
503      * @throws UnknownEntityException if the implementation of Role interface
504      *         could not be determined, or does not exist.
505      */
506     public Class getRoleClass()
507             throws UnknownEntityException
508     {
509         if (roleClass == null)
510         {
511             throw new UnknownEntityException(
512                     "Failed to create a Class object for Role implementation");
513         }
514         return roleClass;
515     }
516 
517     /**
518      * Construct a blank Role object.
519      *
520      * This method calls getRoleClass, and then creates a new object using
521      * the default constructor.
522      *
523      * @return an object implementing Role interface.
524      * @throws UnknownEntityException if the object could not be instantiated.
525      */
526     public Role getRoleInstance()
527             throws UnknownEntityException
528     {
529         Role role;
530 
531         try
532         {
533             role = (Role) getRoleClass().newInstance();
534         }
535         catch (Exception e)
536         {
537             throw new UnknownEntityException("Failed to instantiate a Role implementation object", e);
538         }
539         return role;
540     }
541 
542     /**
543      * Construct a blank Role object.
544      *
545      * This method calls getRoleClass, and then creates a new object using
546      * the default constructor.
547      *
548      * @param roleName The name of the role.
549      *
550      * @return an object implementing Role interface.
551      *
552      * @throws UnknownEntityException if the object could not be instantiated.
553      */
554     public Role getRoleInstance(String roleName)
555             throws UnknownEntityException
556     {
557         Role role = getRoleInstance();
558         role.setName(roleName);
559         return role;
560     }
561 
562     /**
563      * Return a Class object representing the system's chosen implementation of
564      * of ACL interface.
565      *
566      * @return systems's chosen implementation of ACL interface.
567      * @throws UnknownEntityException if the implementation of ACL interface
568      *         could not be determined, or does not exist.
569      */
570     public Class getAclClass()
571             throws UnknownEntityException
572     {
573         if (aclClass == null)
574         {
575             throw new UnknownEntityException(
576                     "Failed to create a Class object for ACL implementation");
577         }
578         return aclClass;
579     }
580 
581     /**
582      * Construct a new ACL object.
583      *
584      * This constructs a new ACL object from the configured class and
585      * initializes it with the supplied roles and permissions.
586      *
587      * @param roles The roles that this ACL should contain
588      * @param permissions The permissions for this ACL
589      *
590      * @return an object implementing ACL interface.
591      * @throws UnknownEntityException if the object could not be instantiated.
592      */
593     public AccessControlList getAclInstance(Map roles, Map permissions)
594             throws UnknownEntityException
595     {
596         Object[] objects = {roles, permissions};
597         String[] signatures = {Map.class.getName(), Map.class.getName()};
598         AccessControlList accessControlList;
599 
600         try
601         {
602             accessControlList =
603                     (AccessControlList) aclFactoryService.getInstance(aclClass.getName(),
604                             objects,
605                             signatures);
606         }
607         catch (Exception e)
608         {
609             throw new UnknownEntityException(
610                     "Failed to instantiate an ACL implementation object", e);
611         }
612 
613         return accessControlList;
614     }
615 
616     /**
617      * Returns the configured UserManager.
618      *
619      * @return An UserManager object
620      */
621     public UserManager getUserManager()
622     {
623         return userManager;
624     }
625 
626     /**
627      * Configure a new user Manager.
628      *
629      * @param userManager An UserManager object
630      */
631     public void setUserManager(UserManager userManager)
632     {
633         this.userManager = userManager;
634     }
635 
636     /**
637      * Check whether a specified user's account exists.
638      *
639      * The login name is used for looking up the account.
640      *
641      * @param user The user to be checked.
642      * @return true if the specified account exists
643      * @throws DataBackendException if there was an error accessing the data
644      *         backend.
645      */
646     public boolean accountExists(User user)
647             throws DataBackendException
648     {
649         return getUserManager().accountExists(user);
650     }
651 
652     /**
653      * Check whether a specified user's account exists.
654      *
655      * The login name is used for looking up the account.
656      *
657      * @param userName The name of the user to be checked.
658      * @return true if the specified account exists
659      * @throws DataBackendException if there was an error accessing the data
660      *         backend.
661      */
662     public boolean accountExists(String userName)
663             throws DataBackendException
664     {
665         return getUserManager().accountExists(userName);
666     }
667 
668     /**
669      * Authenticates an user, and constructs an User object to represent
670      * him/her.
671      *
672      * @param username The user name.
673      * @param password The user password.
674      * @return An authenticated Turbine User.
675      * @throws PasswordMismatchException if the supplied password was incorrect.
676      * @throws UnknownEntityException if the user's account does not
677      *            exist in the database.
678      * @throws DataBackendException if there is a problem accessing the storage.
679      */
680     public User getAuthenticatedUser(String username, String password)
681             throws DataBackendException, UnknownEntityException,
682                    PasswordMismatchException
683     {
684         return getUserManager().retrieve(username, password);
685     }
686 
687     /**
688      * Constructs an User object to represent a registered user of the
689      * application.
690      *
691      * @param username The user name.
692      * @return A Turbine User.
693      * @throws UnknownEntityException if the user's account does not exist
694      * @throws DataBackendException if there is a problem accessing the storage.
695      */
696     public User getUser(String username)
697             throws DataBackendException, UnknownEntityException
698     {
699         return getUserManager().retrieve(username);
700     }
701 
702 
703     /**
704      * Constructs an User object to represent an anonymous user of the
705      * application.
706      *
707      * @return An anonymous Turbine User.
708      * @throws UnknownEntityException if the implementation of User interface
709      *         could not be determined, or does not exist.
710      */
711     public User getAnonymousUser()
712             throws UnknownEntityException
713     {
714         User user = getUserInstance();
715         user.setName("");
716         return user;
717     }
718 
719     /**
720      * Checks whether a passed user object matches the anonymous user pattern
721      * according to the configured user manager
722      *
723      * @param user An user object
724      *
725      * @return True if this is an anonymous user
726      *
727      */
728     public boolean isAnonymousUser(User user)
729     {
730         // Either just null, the name is null or the name is the empty string
731         return (user == null) || StringUtils.isEmpty(user.getName());
732     }
733 
734     /**
735      * Saves User's data in the permanent storage. The user account is required
736      * to exist in the storage.
737      *
738      * @param user the User object to save
739      * @throws UnknownEntityException if the user's account does not
740      *         exist in the database.
741      * @throws DataBackendException if there is a problem accessing the storage.
742      */
743     public void saveUser(User user)
744             throws UnknownEntityException, DataBackendException
745     {
746         getUserManager().store(user);
747     }
748 
749     /**
750      * Saves User data when the session is unbound. The user account is required
751      * to exist in the storage.
752      *
753      * LastLogin, AccessCounter, persistent pull tools, and any data stored
754      * in the permData hashtable that is not mapped to a column will be saved.
755      *
756      * @exception UnknownEntityException if the user's account does not
757      *            exist in the database.
758      * @exception DataBackendException if there is a problem accessing the
759      *            storage.
760      */
761     public void saveOnSessionUnbind(User user)
762             throws UnknownEntityException, DataBackendException
763     {
764         userManager.saveOnSessionUnbind(user);
765     }
766 
767     /**
768      * Creates new user account with specified attributes.
769      *
770      * @param user the object describing account to be created.
771      * @param password The password to use for the account.
772      *
773      * @throws DataBackendException if there was an error accessing the
774      *         data backend.
775      * @throws EntityExistsException if the user account already exists.
776      */
777     public void addUser(User user, String password)
778             throws DataBackendException, EntityExistsException
779     {
780         getUserManager().createAccount(user, password);
781     }
782 
783     /**
784      * Removes an user account from the system.
785      *
786      * @param user the object describing the account to be removed.
787      * @throws DataBackendException if there was an error accessing the data
788      *         backend.
789      * @throws UnknownEntityException if the user account is not present.
790      */
791     public void removeUser(User user)
792             throws DataBackendException, UnknownEntityException
793     {
794         // revoke all roles form the user
795         revokeAll(user);
796 
797         getUserManager().removeAccount(user);
798     }
799 
800     /**
801      * Change the password for an User.
802      *
803      * @param user an User to change password for.
804      * @param oldPassword the current password supplied by the user.
805      * @param newPassword the current password requested by the user.
806      * @throws PasswordMismatchException if the supplied password was incorrect.
807      * @throws UnknownEntityException if the user's record does not
808      *            exist in the database.
809      * @throws DataBackendException if there is a problem accessing the storage.
810      */
811     public void changePassword(User user, String oldPassword,
812             String newPassword)
813             throws PasswordMismatchException, UnknownEntityException,
814                    DataBackendException
815     {
816         getUserManager().changePassword(user, oldPassword, newPassword);
817     }
818 
819     /**
820      * Forcibly sets new password for an User.
821      *
822      * This is supposed by the administrator to change the forgotten or
823      * compromised passwords. Certain implementatations of this feature
824      * would require administrative level access to the authenticating
825      * server / program.
826      *
827      * @param user an User to change password for.
828      * @param password the new password.
829      * @throws UnknownEntityException if the user's record does not
830      *            exist in the database.
831      * @throws DataBackendException if there is a problem accessing the storage.
832      */
833     public void forcePassword(User user, String password)
834             throws UnknownEntityException, DataBackendException
835     {
836         getUserManager().forcePassword(user, password);
837     }
838 
839     /**
840      * Acquire a shared lock on the security information repository.
841      *
842      * Methods that read security information need to invoke this
843      * method at the beginning of their body.
844      */
845     protected synchronized void lockShared()
846     {
847         readerCount++;
848     }
849 
850     /**
851      * Release a shared lock on the security information repository.
852      *
853      * Methods that read security information need to invoke this
854      * method at the end of their body.
855      */
856     protected synchronized void unlockShared()
857     {
858         readerCount--;
859         this.notify();
860     }
861 
862     /**
863      * Acquire an exclusive lock on the security information repository.
864      *
865      * Methods that modify security information need to invoke this
866      * method at the beginning of their body. Note! Those methods must
867      * be <code>synchronized</code> themselves!
868      */
869     protected void lockExclusive()
870     {
871         while (readerCount > 0)
872         {
873             try
874             {
875                 this.wait();
876             }
877             catch (InterruptedException e)
878             {
879             }
880         }
881     }
882 
883     /**
884      * Release an exclusive lock on the security information repository.
885      *
886      * This method is provided only for completeness. It does not really
887      * do anything. Note! Methods that modify security information
888      * must be <code>synchronized</code>!
889      */
890     protected void unlockExclusive()
891     {
892         // do nothing
893     }
894 
895     /**
896      * Provides a reference to the Group object that represents the
897      * <a href="#global">global group</a>.
898      *
899      * @return a Group object that represents the global group.
900      */
901     public Group getGlobalGroup()
902     {
903         if (globalGroup == null)
904         {
905             synchronized (BaseSecurityService.class)
906             {
907                 if (globalGroup == null)
908                 {
909                     try
910                     {
911                         globalGroup = getAllGroups()
912                                 .getGroupByName(Group.GLOBAL_GROUP_NAME);
913                     }
914                     catch (DataBackendException e)
915                     {
916                         log.error("Failed to retrieve global group object: ", e);
917                     }
918                 }
919             }
920         }
921         return globalGroup;
922     }
923 
924     /**
925      * Retrieve a Group object with specified name.
926      *
927      * @param name the name of the Group.
928      * @return an object representing the Group with specified name.
929      * @throws DataBackendException if there was an error accessing the
930      *         data backend.
931      * @throws UnknownEntityException if the group does not exist.
932      */
933     public Group getGroupByName(String name)
934             throws DataBackendException, UnknownEntityException
935     {
936         Group group = getAllGroups().getGroupByName(name);
937         if (group == null)
938         {
939             throw new UnknownEntityException(
940                     "The specified group does not exist");
941         }
942         return group;
943     }
944 
945     /**
946      * Retrieve a Group object with specified Id.
947      *
948      * @param id the id of the Group.
949      * @return an object representing the Group with specified name.
950      * @throws UnknownEntityException if the permission does not
951      *            exist in the database.
952      * @throws DataBackendException if there is a problem accessing the
953      *            storage.
954      */
955     public Group getGroupById(int id)
956             throws DataBackendException, UnknownEntityException
957     {
958         Group group = getAllGroups().getGroupById(id);
959         if (group == null)
960         {
961             throw new UnknownEntityException(
962                     "The specified group does not exist");
963         }
964         return group;
965     }
966 
967     /**
968      * Retrieve a Role object with specified name.
969      *
970      * @param name the name of the Role.
971      * @return an object representing the Role with specified name.
972      * @throws DataBackendException if there was an error accessing the
973      *         data backend.
974      * @throws UnknownEntityException if the role does not exist.
975      */
976     public Role getRoleByName(String name)
977             throws DataBackendException, UnknownEntityException
978     {
979         Role role = getAllRoles().getRoleByName(name);
980         if (role == null)
981         {
982             throw new UnknownEntityException(
983                     "The specified role does not exist");
984         }
985         role.setPermissions(getPermissions(role));
986         return role;
987     }
988 
989     /**
990      * Retrieve a Role object with specified Id.
991      * @param id the id of the Role.
992      * @return an object representing the Role with specified name.
993      * @throws UnknownEntityException if the permission does not
994      *            exist in the database.
995      * @throws DataBackendException if there is a problem accessing the
996      *            storage.
997      */
998     public Role getRoleById(int id)
999             throws DataBackendException,
1000                    UnknownEntityException
1001     {
1002         Role role = getAllRoles().getRoleById(id);
1003         if (role == null)
1004         {
1005             throw new UnknownEntityException(
1006                     "The specified role does not exist");
1007         }
1008         role.setPermissions(getPermissions(role));
1009         return role;
1010     }
1011 
1012     /**
1013      * Retrieve a Permission object with specified name.
1014      *
1015      * @param name the name of the Permission.
1016      * @return an object representing the Permission with specified name.
1017      * @throws DataBackendException if there was an error accessing the
1018      *         data backend.
1019      * @throws UnknownEntityException if the permission does not exist.
1020      */
1021     public Permission getPermissionByName(String name)
1022             throws DataBackendException, UnknownEntityException
1023     {
1024         Permission permission = getAllPermissions().getPermissionByName(name);
1025         if (permission == null)
1026         {
1027             throw new UnknownEntityException(
1028                     "The specified permission does not exist");
1029         }
1030         return permission;
1031     }
1032 
1033     /**
1034      * Retrieve a Permission object with specified Id.
1035      *
1036      * @param id the id of the Permission.
1037      * @return an object representing the Permission with specified name.
1038      * @throws UnknownEntityException if the permission does not
1039      *            exist in the database.
1040      * @throws DataBackendException if there is a problem accessing the
1041      *            storage.
1042      */
1043     public Permission getPermissionById(int id)
1044             throws DataBackendException,
1045                    UnknownEntityException
1046     {
1047         Permission permission = getAllPermissions().getPermissionById(id);
1048         if (permission == null)
1049         {
1050             throw new UnknownEntityException(
1051                     "The specified permission does not exist");
1052         }
1053         return permission;
1054     }
1055 
1056     /**
1057      * Retrieves all groups defined in the system.
1058      *
1059      * @return the names of all groups defined in the system.
1060      * @throws DataBackendException if there was an error accessing the
1061      *         data backend.
1062      */
1063     public abstract GroupSet getAllGroups()
1064             throws DataBackendException;
1065 
1066     /**
1067      * Retrieves all roles defined in the system.
1068      *
1069      * @return the names of all roles defined in the system.
1070      * @throws DataBackendException if there was an error accessing the
1071      *         data backend.
1072      */
1073     public abstract RoleSet getAllRoles()
1074             throws DataBackendException;
1075 
1076     /**
1077      * Retrieves all permissions defined in the system.
1078      *
1079      * @return the names of all roles defined in the system.
1080      * @throws DataBackendException if there was an error accessing the
1081      *         data backend.
1082      */
1083     public abstract PermissionSet getAllPermissions()
1084             throws DataBackendException;
1085 }