001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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>
049     * -instance.
050     * <p>
051     * In order to use methods that read from the registry, a <code>SecurityAccess</code> instance has
052     * to be acquired first:
053     * <p>
054     * <b>Example Code: </b>
055     *
056     * <pre>
057     * SecurityAccess access = SecurityAccessManager.getInstance();
058     *
059     * ReadToken accessToken = access.acquireReadToken();
060     *
061     * Role role = access.getRoleById( accessToken, 1 );
062     * </pre>
063     *
064     * <p>
065     * If write access is needed as well, one has to acquire the exclusive
066     * <code>SecurityTransaction</code>. This is only possible if the <code>User</code> has the
067     * "write"-privilege.
068     * <p>
069     * <b>Example Code: </b>
070     *
071     * <pre>
072     *   SecurityAccess access = SecurityAccess.getInstance ();
073     *   SecurityTransaction lock = access.acquireSecurityTransaction (user);
074     *   access.registerUser (lock, &quot;TESTUSER&quot;);
075     *   ...
076     *   access.commitTransaction (lock);
077     *   // after committing changes are made persistent
078     * </pre>
079     *
080     * @author <a href="mschneider@lat-lon.de">Markus Schneider </a>
081     * @author last edited by: $Author:wanhoff$
082     *
083     * @version $Revision: 18195 $, $Date:26.03.2007$
084     *
085     */
086    public class SecurityAccessManager {
087    
088        private static final ILogger LOG = LoggerFactory.getLogger( SecurityAccessManager.class );
089    
090        private static SecurityAccessManager instance = null;
091    
092        private SecurityRegistry registry = null;
093    
094        // the currently valid (exclusive) transaction
095        private SecurityTransaction currentTransaction;
096    
097        // maximal duration that a transaction lasts (milliseconds)
098        private long timeout;
099    
100        // admin user (predefined)
101        private User adminUser;
102    
103        // admin group (predefined)
104        // private Group adminGroup;
105    
106        // admin role (predefined)
107        private Role adminRole;
108    
109        /**
110         * Initializes the <code>SecurityAccessManager</code> -singleton with the given
111         * <code>Registry</code> -instance.
112         *
113         * @param registryClassName
114         * @param registryProperties
115         * @param timeout
116         * @throws GeneralSecurityException
117         */
118        public static synchronized void initialize( String registryClassName, Properties registryProperties, long timeout )
119                                throws GeneralSecurityException {
120            if ( SecurityAccessManager.instance != null ) {
121                throw new GeneralSecurityException( "SecurityAccessManager may only be initialized once." );
122            }
123            SecurityRegistry registry;
124            try {
125                registry = (SecurityRegistry) Class.forName( registryClassName ).newInstance();
126            } catch ( Exception e ) {
127                throw new GeneralSecurityException( "Unable to instantiate RegistryClass for class name '"
128                                                    + registryClassName + "': " + e.getMessage() );
129            }
130            registry.initialize( registryProperties );
131            SecurityAccessManager.instance = new SecurityAccessManager( registry, timeout );
132        }
133    
134        /**
135         * @return true if there is an instance
136         */
137        public static boolean isInitialized() {
138            return SecurityAccessManager.instance != null;
139        }
140    
141        /**
142         * Returns the only instance of this class.
143         *
144         * @return the only instance of this class.
145         * @throws GeneralSecurityException
146         *
147         */
148        public static synchronized SecurityAccessManager getInstance()
149                                throws GeneralSecurityException {
150            if ( SecurityAccessManager.instance == null ) {
151                throw new GeneralSecurityException( "SecurityAccessManager has not been initialized yet." );
152            }
153            return SecurityAccessManager.instance;
154        }
155    
156        /**
157         * This method is only to be used to get an initial <code>User</code> object. (Otherwise one
158         * would need a <code>User</code> to perform a <code>User</code> lookup.)
159         *
160         * @param name
161         * @return the user
162         * @throws GeneralSecurityException
163         */
164        public User getUserByName( String name )
165                                throws GeneralSecurityException {
166            return registry.getUserByName( null, name );
167        }
168    
169        /**
170         * Tries to acquire a <code>SecurityAccess</code> -instance.
171         *
172         * @param user
173         * @return the instance
174         *
175         * @throws GeneralSecurityException
176         * @throws UnauthorizedException
177         */
178        public SecurityAccess acquireAccess( User user )
179                                throws GeneralSecurityException, UnauthorizedException {
180    
181            if ( user == null ) {
182                throw new UnauthorizedException( "Can't acquire security access for anonymous user" );
183            }
184            if ( !user.isAuthenticated() ) {
185                throw new UnauthorizedException( "Can't acquire security access for '" + user.getName()
186                                                 + "'. User has not been authorized to the system." );
187            }
188    
189            return new SecurityAccess( user, registry );
190        }
191    
192        /**
193         * Tries to acquire the <code>SecurityTransaction</code> for the given <code>User</code>.
194         * Only possibly for <code>User</code> s that have the "modify"-privilege.
195         * <p>
196         * NOTE: The implementation checks if the <code>currentTransaction</code> timed out BEFORE it
197         * checks if the user is allowed to write to the registry at all. This is because some
198         * JDBC-drivers (at least the JDBC-ODBC- bridge together with Microsoft Access (tm)) have been
199         * observed to return strange results sometimes when there's a transaction still going on (so
200         * that the privileges of the user cannot be retrieved reliably from the registry).
201         *
202         * @param user
203         * @return the transaction
204         *
205         * @throws GeneralSecurityException
206         * @throws UnauthorizedException
207         */
208        public SecurityTransaction acquireTransaction( User user )
209                                throws GeneralSecurityException, UnauthorizedException {
210            if ( currentTransaction != null ) {
211                if ( System.currentTimeMillis() < currentTransaction.getTimestamp() + timeout ) {
212                    throw new ReadWriteLockInUseException( "Can't get ReadWriteLock, because it is currently in use." );
213                }
214                try {
215                    registry.abortTransaction( currentTransaction );
216                } catch ( GeneralSecurityException e ) {
217                    e.printStackTrace();
218                }
219    
220            }
221            if ( !user.isAuthenticated() ) {
222                throw new UnauthorizedException( "Can't acquire ReadWriteLock for '" + user.getName()
223                                                 + "'. User has not been authorized " + "to the system." );
224            }
225            SecurityAccess tempAccess = new SecurityAccess( user, registry );
226            if ( !user.hasPrivilege( tempAccess, tempAccess.getPrivilegeByName( "write" ) ) ) {
227                throw new UnauthorizedException( "Can't acquire transaction: " + "User is not allowed to perform changes." );
228            }
229            currentTransaction = new SecurityTransaction( user, registry, adminRole );
230            registry.beginTransaction( currentTransaction );
231            return currentTransaction;
232        }
233    
234        /**
235         * Private constructor to enforce the singleton pattern.
236         *
237         * @param registry
238         * @param timeout
239         * @throws GeneralSecurityException
240         */
241        private SecurityAccessManager( SecurityRegistry registry, long timeout ) throws GeneralSecurityException {
242            this.registry = registry;
243            this.timeout = timeout;
244    
245            adminUser = getUserByName( "SEC_ADMIN" );
246            SecurityAccess access = new SecurityAccess( adminUser, registry );
247            // TODO adminGroup will never been read; can be removed?
248            // adminGroup = access.getGroupByName( "SEC_ADMIN" );
249            adminRole = registry.getRoleByName( access, "SEC_ADMIN" );
250        }
251    
252        /**
253         * Verifies that the submitted <code>Transaction</code> is valid. There are two ways for it to
254         * become invalid:
255         * <ul>
256         * <li>it is too old (and timed out)
257         * <li>it ended (has been aborted / committed)
258         * </ul>
259         *
260         * @param transaction
261         * @throws ReadWriteLockInvalidException
262         * @throws GeneralSecurityException
263         *             if transaction is invalid
264         */
265        void verify( SecurityTransaction transaction )
266                                throws ReadWriteLockInvalidException {
267            if ( transaction == null || transaction != currentTransaction ) {
268                throw new ReadWriteLockInvalidException( "The SecurityTransaction is invalid." );
269            } else if ( System.currentTimeMillis() > currentTransaction.getTimestamp() + timeout ) {
270                currentTransaction = null;
271                try {
272                    registry.abortTransaction( transaction );
273                } catch ( GeneralSecurityException e ) {
274                    e.printStackTrace();
275                }
276                LOG.logInfo( "timeout: " + timeout );
277                LOG.logInfo( "current: " + System.currentTimeMillis() );
278                LOG.logInfo( "lock ts: " + currentTransaction.getTimestamp() );
279    
280                throw new ReadWriteLockInvalidException( "The SecurityTransaction timed out." );
281            }
282            currentTransaction.renew();
283        }
284    
285        /**
286         * Ends the current transaction and commits all changes to the <code>Registry</code>.
287         *
288         * @param transaction
289         *
290         * @throws GeneralSecurityException
291         */
292        public void commitTransaction( SecurityTransaction transaction )
293                                throws GeneralSecurityException {
294            verify( transaction );
295            currentTransaction = null;
296            registry.commitTransaction( transaction );
297        }
298    
299        /**
300         * Aborts the current transaction and undoes all changes made to the <code>Registry</code>.
301         *
302         * @param lock
303         *
304         * @throws GeneralSecurityException
305         */
306        public void abortTransaction( SecurityTransaction lock )
307                                throws GeneralSecurityException {
308            verify( lock );
309            currentTransaction = null;
310            registry.abortTransaction( lock );
311        }
312    }