001    package org.apache.turbine.services.security;
002    
003    
004    /*
005     * Licensed to the Apache Software Foundation (ASF) under one
006     * or more contributor license agreements.  See the NOTICE file
007     * distributed with this work for additional information
008     * regarding copyright ownership.  The ASF licenses this file
009     * to you under the Apache License, Version 2.0 (the
010     * "License"); you may not use this file except in compliance
011     * with the License.  You may obtain a copy of the License at
012     *
013     *   http://www.apache.org/licenses/LICENSE-2.0
014     *
015     * Unless required by applicable law or agreed to in writing,
016     * software distributed under the License is distributed on an
017     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
018     * KIND, either express or implied.  See the License for the
019     * specific language governing permissions and limitations
020     * under the License.
021     */
022    
023    
024    import java.util.Map;
025    
026    import javax.servlet.ServletConfig;
027    
028    import org.apache.commons.configuration.Configuration;
029    import org.apache.commons.lang.StringUtils;
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.apache.fulcrum.crypto.CryptoAlgorithm;
033    import org.apache.fulcrum.crypto.CryptoService;
034    import org.apache.fulcrum.factory.FactoryService;
035    import org.apache.turbine.om.security.Group;
036    import org.apache.turbine.om.security.Permission;
037    import org.apache.turbine.om.security.Role;
038    import org.apache.turbine.om.security.User;
039    import org.apache.turbine.services.InitializationException;
040    import org.apache.turbine.services.ServiceManager;
041    import org.apache.turbine.services.TurbineBaseService;
042    import org.apache.turbine.services.TurbineServices;
043    import org.apache.turbine.util.security.AccessControlList;
044    import org.apache.turbine.util.security.DataBackendException;
045    import org.apache.turbine.util.security.EntityExistsException;
046    import org.apache.turbine.util.security.GroupSet;
047    import org.apache.turbine.util.security.PasswordMismatchException;
048    import org.apache.turbine.util.security.PermissionSet;
049    import org.apache.turbine.util.security.RoleSet;
050    import org.apache.turbine.util.security.UnknownEntityException;
051    
052    /**
053     * This is a common subset of SecurityService implementation.
054     *
055     * Provided functionality includes:
056     * <ul>
057     * <li> methods for retrieving User objects, that delegates functionality
058     *      to the pluggable implementations of the User interface.
059     * <li> synchronization mechanism for methods reading/modifying the security
060     *      information, that guarantees that multiple threads may read the
061     *      information concurrently, but threads that modify the information
062     *      acquires exclusive access.
063     * <li> implementation of convenience methods for retrieving security entities
064     *      that maintain in-memory caching of objects for fast access.
065     * </ul>
066     *
067     * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
068     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
069     * @author <a href="mailto:marco@intermeta.de">Marco Kn&uuml;ttel</a>
070     * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
071     * @version $Id: BaseSecurityService.java 1096130 2011-04-23 10:37:19Z ludwig $
072     */
073    public abstract class BaseSecurityService
074            extends TurbineBaseService
075            implements SecurityService
076    {
077        /** The number of threads concurrently reading security information */
078        private int readerCount = 0;
079    
080        /** The instance of UserManager the SecurityService uses */
081        private UserManager userManager = null;
082    
083        /** The class of User the SecurityService uses */
084        private Class userClass = null;
085    
086        /** The class of Group the SecurityService uses */
087        private Class groupClass = null;
088    
089        /** The class of Permission the SecurityService uses */
090        private Class permissionClass = null;
091    
092        /** The class of Role the SecurityService uses */
093        private Class roleClass = null;
094    
095        /** The class of ACL the SecurityService uses */
096        private Class aclClass = null;
097    
098        /** A factory to construct ACL Objects */
099        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    }