001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/security/drm/SecurityAccessManager.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    package org.deegree.security.drm;
037    
038    import java.util.Properties;
039    
040    import org.deegree.framework.log.ILogger;
041    import org.deegree.framework.log.LoggerFactory;
042    import org.deegree.security.GeneralSecurityException;
043    import org.deegree.security.UnauthorizedException;
044    import org.deegree.security.drm.model.Role;
045    import org.deegree.security.drm.model.User;
046    
047    /**
048     * This singleton manages access to the data stored in an associated <code>SecurityRegistry</code> -instance.
049     * <p>
050     * In order to use methods that read from the registry, a <code>SecurityAccess</code> instance has to be acquired first:
051     * <p>
052     * <b>Example Code: </b>
053     * 
054     * <pre>
055     * SecurityAccess access = SecurityAccessManager.getInstance();
056     * 
057     * ReadToken accessToken = access.acquireReadToken();
058     * 
059     * Role role = access.getRoleById( accessToken, 1 );
060     * </pre>
061     * 
062     * <p>
063     * If write access is needed as well, one has to acquire the exclusive <code>SecurityTransaction</code>. This is only
064     * possible if the <code>User</code> has the "write"-privilege.
065     * <p>
066     * <b>Example Code: </b>
067     * 
068     * <pre>
069     *   SecurityAccess access = SecurityAccess.getInstance ();
070     *   SecurityTransaction lock = access.acquireSecurityTransaction (user);
071     *   access.registerUser (lock, &quot;TESTUSER&quot;);
072     *   ...
073     *   access.commitTransaction (lock);
074     *   // after committing changes are made persistent
075     * </pre>
076     * 
077     * @author <a href="mschneider@lat-lon.de">Markus Schneider </a>
078     * @author last edited by: $Author:wanhoff$
079     * 
080     * @version $Revision: 23585 $, $Date:26.03.2007$
081     * 
082     */
083    public class SecurityAccessManager {
084    
085        private static final ILogger LOG = LoggerFactory.getLogger( SecurityAccessManager.class );
086    
087        private static SecurityAccessManager instance = null;
088    
089        private SecurityRegistry registry = null;
090    
091        // the currently valid (exclusive) transaction
092        private SecurityTransaction currentTransaction;
093    
094        // maximal duration that a transaction lasts (milliseconds)
095        private long timeout;
096    
097        // admin user (predefined)
098        private User adminUser;
099    
100        // admin group (predefined)
101        // private Group adminGroup;
102    
103        // admin role (predefined)
104        private Role adminRole;
105    
106        /**
107         * Initializes the <code>SecurityAccessManager</code> -singleton with the given <code>Registry</code> -instance.
108         * 
109         * @param registryClassName
110         * @param registryProperties
111         * @param timeout
112         * @throws GeneralSecurityException
113         */
114        public static synchronized void initialize( String registryClassName, Properties registryProperties, long timeout )
115                                throws GeneralSecurityException {
116            if ( SecurityAccessManager.instance != null ) {
117                throw new GeneralSecurityException( "SecurityAccessManager may only be initialized once." );
118            }
119            SecurityRegistry registry;
120            try {
121                registry = (SecurityRegistry) Class.forName( registryClassName ).newInstance();
122            } catch ( Exception e ) {
123                throw new GeneralSecurityException( "Unable to instantiate RegistryClass for class name '"
124                                                    + registryClassName + "': " + e.getMessage() );
125            }
126            registry.initialize( registryProperties );
127            SecurityAccessManager.instance = new SecurityAccessManager( registry, timeout );
128        }
129    
130        /**
131         * @return true if there is an instance
132         */
133        public static boolean isInitialized() {
134            return SecurityAccessManager.instance != null;
135        }
136    
137        /**
138         * Returns the only instance of this class.
139         * 
140         * @return the only instance of this class.
141         * @throws GeneralSecurityException
142         * 
143         */
144        public static synchronized SecurityAccessManager getInstance()
145                                throws GeneralSecurityException {
146            if ( SecurityAccessManager.instance == null ) {
147                throw new GeneralSecurityException( "SecurityAccessManager has not been initialized yet." );
148            }
149            return SecurityAccessManager.instance;
150        }
151    
152        /**
153         * This method is only to be used to get an initial <code>User</code> object. (Otherwise one would need a
154         * <code>User</code> to perform a <code>User</code> lookup.)
155         * 
156         * @param name
157         * @return the user
158         * @throws GeneralSecurityException
159         */
160        public User getUserByName( String name )
161                                throws GeneralSecurityException {
162            return registry.getUserByName( null, name );
163        }
164    
165        /**
166         * Tries to acquire a <code>SecurityAccess</code> -instance.
167         * 
168         * @param user
169         * @return the instance
170         * 
171         * @throws GeneralSecurityException
172         * @throws UnauthorizedException
173         */
174        public SecurityAccess acquireAccess( User user )
175                                throws GeneralSecurityException, UnauthorizedException {
176    
177            if ( user == null ) {
178                throw new UnauthorizedException( "Can't acquire security access for anonymous user" );
179            }
180            if ( !user.isAuthenticated() ) {
181                throw new UnauthorizedException( "Can't acquire security access for '" + user.getName()
182                                                 + "'. User has not been authorized to the system." );
183            }
184    
185            return new SecurityAccess( user, registry );
186        }
187    
188        /**
189         * Tries to acquire the <code>SecurityTransaction</code> for the given <code>User</code>. Only possibly for
190         * <code>User</code> s that have the "modify"-privilege.
191         * <p>
192         * NOTE: The implementation checks if the <code>currentTransaction</code> timed out BEFORE it checks if the user is
193         * allowed to write to the registry at all. This is because some JDBC-drivers (at least the JDBC-ODBC- bridge
194         * together with Microsoft Access (tm)) have been observed to return strange results sometimes when there's a
195         * transaction still going on (so that the privileges of the user cannot be retrieved reliably from the registry).
196         * 
197         * @param user
198         * @return the transaction
199         * 
200         * @throws GeneralSecurityException
201         * @throws UnauthorizedException
202         */
203        public SecurityTransaction acquireTransaction( User user )
204                                throws GeneralSecurityException, UnauthorizedException {
205            if ( currentTransaction != null ) {
206                if ( System.currentTimeMillis() < currentTransaction.getTimestamp() + timeout ) {
207                    throw new ReadWriteLockInUseException( "Can't get ReadWriteLock, because it is currently in use." );
208                }
209                try {
210                    registry.abortTransaction( currentTransaction );
211                } catch ( GeneralSecurityException e ) {
212                    e.printStackTrace();
213                }
214    
215            }
216            if ( !user.isAuthenticated() ) {
217                throw new UnauthorizedException( "Can't acquire ReadWriteLock for '" + user.getName()
218                                                 + "'. User has not been authorized " + "to the system." );
219            }
220            SecurityAccess tempAccess = new SecurityAccess( user, registry );
221            if ( !user.hasPrivilege( tempAccess, tempAccess.getPrivilegeByName( "write" ) ) ) {
222                throw new UnauthorizedException( "Can't acquire transaction: " + "User is not allowed to perform changes." );
223            }
224            currentTransaction = new SecurityTransaction( user, registry, adminRole );
225            registry.beginTransaction( currentTransaction );
226            return currentTransaction;
227        }
228    
229        /**
230         * Private constructor to enforce the singleton pattern.
231         * 
232         * @param registry
233         * @param timeout
234         * @throws GeneralSecurityException
235         */
236        private SecurityAccessManager( SecurityRegistry registry, long timeout ) throws GeneralSecurityException {
237            this.registry = registry;
238            this.timeout = timeout;
239    
240            adminUser = getUserByName( "SEC_ADMIN" );
241            SecurityAccess access = new SecurityAccess( adminUser, registry );
242            // TODO adminGroup will never been read; can be removed?
243            // adminGroup = access.getGroupByName( "SEC_ADMIN" );
244            adminRole = registry.getRoleByName( access, "SEC_ADMIN" );
245        }
246    
247        /**
248         * Verifies that the submitted <code>Transaction</code> is valid. There are two ways for it to become invalid:
249         * <ul>
250         * <li>it is too old (and timed out)
251         * <li>it ended (has been aborted / committed)
252         * </ul>
253         * 
254         * @param transaction
255         * @throws ReadWriteLockInvalidException
256         * @throws GeneralSecurityException
257         *             if transaction is invalid
258         */
259        void verify( SecurityTransaction transaction )
260                                throws ReadWriteLockInvalidException {
261            if ( transaction == null || transaction != currentTransaction ) {
262                throw new ReadWriteLockInvalidException( "The SecurityTransaction is invalid." );
263            } else if ( System.currentTimeMillis() > currentTransaction.getTimestamp() + timeout ) {
264                long ts = currentTransaction.getTimestamp();
265                currentTransaction = null;
266                try {
267                    registry.abortTransaction( transaction );
268                } catch ( GeneralSecurityException e ) {
269                    e.printStackTrace();
270                }
271                LOG.logInfo( "timeout: " + timeout );
272                LOG.logInfo( "current: " + System.currentTimeMillis() );
273                LOG.logInfo( "lock ts: " + ts );
274    
275                throw new ReadWriteLockInvalidException( "The SecurityTransaction timed out." );
276            }
277            currentTransaction.renew();
278        }
279    
280        /**
281         * Ends the current transaction and commits all changes to the <code>Registry</code>.
282         * 
283         * @param transaction
284         * 
285         * @throws GeneralSecurityException
286         */
287        public void commitTransaction( SecurityTransaction transaction )
288                                throws GeneralSecurityException {
289            verify( transaction );
290            currentTransaction = null;
291            registry.commitTransaction( transaction );
292        }
293    
294        /**
295         * Aborts the current transaction and undoes all changes made to the <code>Registry</code>.
296         * 
297         * @param lock
298         * 
299         * @throws GeneralSecurityException
300         */
301        public void abortTransaction( SecurityTransaction lock )
302                                throws GeneralSecurityException {
303            verify( lock );
304            currentTransaction = null;
305            registry.abortTransaction( lock );
306        }
307    }