package org.deegree.security.drm;
import java.util.Properties;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.security.GeneralSecurityException;
import org.deegree.security.UnauthorizedException;
import org.deegree.security.drm.model.Role;
import org.deegree.security.drm.model.User;
/**
* This singleton manages access to the data stored in an associated <code>SecurityRegistry</code>
* -instance.
* <p>
* In order to use methods that read from the registry, a <code>SecurityAccess</code> instance has
* to be acquired first:
* <p>
* <b>Example Code: </b>
*
* <pre>
* SecurityAccess access = SecurityAccessManager.getInstance();
*
* ReadToken accessToken = access.acquireReadToken();
*
* Role role = access.getRoleById( accessToken, 1 );
* </pre>
*
* <p>
* If write access is needed as well, one has to acquire the exclusive
* <code>SecurityTransaction</code>. This is only possible if the <code>User</code> has the
* "write"-privilege.
* <p>
* <b>Example Code: </b>
*
* <pre>
* SecurityAccess access = SecurityAccess.getInstance ();
* SecurityTransaction lock = access.acquireSecurityTransaction (user);
* access.registerUser (lock, "TESTUSER");
* ...
* access.commitTransaction (lock);
* // after committing changes are made persistent
* </pre>
*
* @author <a href="mschneider@lat-lon.de">Markus Schneider </a>
* @stereotype singleton
* @version $Revision: 1.10 $
*/
public class SecurityAccessManager {
private static final ILogger LOG = LoggerFactory.getLogger( SecurityAccessManager.class );
private static SecurityAccessManager instance = null;
private SecurityRegistry registry = null;
// the currently valid (exclusive) transaction
private SecurityTransaction currentTransaction;
// maximal duration that a transaction lasts (milliseconds)
private long timeout;
// admin user (predefined)
private User adminUser;
// admin group (predefined)
//private Group adminGroup;
// admin role (predefined)
private Role adminRole;
/**
* Initializes the <code>SecurityAccessManager</code> -singleton with the given
* <code>Registry</code> -instance.
*
* @param registryClassName
* @param registryProperties
* @param timeout
* @throws GeneralSecurityException
*/
public static synchronized void initialize( String registryClassName,
Properties registryProperties, long timeout )
throws GeneralSecurityException {
if ( SecurityAccessManager.instance != null ) {
throw new GeneralSecurityException( "SecurityAccessManager may only be initialized once." );
}
SecurityRegistry registry;
try {
registry = (SecurityRegistry) Class.forName( registryClassName ).newInstance();
} catch ( Exception e ) {
throw new GeneralSecurityException( "Unable to instantiate RegistryClass for class name '"
+ registryClassName + "': "
+ e.getMessage() );
}
registry.initialize( registryProperties );
SecurityAccessManager.instance = new SecurityAccessManager( registry, timeout );
}
/**
* @return true if there is an instance
*/
public static boolean isInitialized() {
return SecurityAccessManager.instance != null;
}
/**
* Returns the only instance of this class.
* @return
* @throws GeneralSecurityException
*
*/
public static synchronized SecurityAccessManager getInstance()
throws GeneralSecurityException {
if ( SecurityAccessManager.instance == null ) {
throw new GeneralSecurityException( "SecurityAccessManager has not been initialized yet." );
}
return SecurityAccessManager.instance;
}
/**
* This method is only to be used to get an initial <code>User</code> object. (Otherwise one
* would need a <code>User</code> to perform a <code>User</code> lookup.)
*
* @param name
* @return
* @throws GeneralSecurityException
*/
public User getUserByName( String name )
throws GeneralSecurityException {
return registry.getUserByName( null, name );
}
/**
* Tries to acquire a <code>SecurityAccess</code> -instance.
* @param user
* @return
*
* @throws GeneralSecurityException
* @throws UnauthorizedException
*/
public SecurityAccess acquireAccess( User user )
throws GeneralSecurityException, UnauthorizedException {
if ( user == null ) {
throw new UnauthorizedException( "Can't acquire security access for anonymous user" );
}
if ( !user.isAuthenticated() ) {
throw new UnauthorizedException( "Can't acquire security access for '" + user.getName()
+ "'. User has not been authorized to the system." );
}
return new SecurityAccess( user, registry );
}
/**
* Tries to acquire the <code>SecurityTransaction</code> for the given <code>User</code>.
* Only possibly for <code>User</code> s that have the "modify"-privilege.
* <p>
* NOTE: The implementation checks if the <code>currentTransaction</code> timed out BEFORE it
* checks if the user is allowed to write to the registry at all. This is because some
* JDBC-drivers (at least the JDBC-ODBC- bridge together with Microsoft Access (tm)) have been
* observed to return strange results sometimes when there's a transaction still going on (so
* that the privileges of the user cannot be retrieved reliably from the registry).
* @param user
* @return
*
* @throws GeneralSecurityException
* @throws UnauthorizedException
*/
public SecurityTransaction acquireTransaction( User user )
throws GeneralSecurityException, UnauthorizedException {
if ( currentTransaction != null ) {
if ( System.currentTimeMillis() < currentTransaction.getTimestamp() + timeout ) {
throw new ReadWriteLockInUseException(
"Can't get ReadWriteLock, because it is currently in use." );
}
try {
registry.abortTransaction( currentTransaction );
} catch ( GeneralSecurityException e ) {
e.printStackTrace();
}
}
if ( !user.isAuthenticated() ) {
throw new UnauthorizedException( "Can't acquire ReadWriteLock for '" + user.getName()
+ "'. User has not been authorized "
+ "to the system." );
}
SecurityAccess tempAccess = new SecurityAccess( user, registry );
if ( !user.hasPrivilege( tempAccess, tempAccess.getPrivilegeByName( "write" ) ) ) {
throw new UnauthorizedException( "Can't acquire transaction: "
+ "User is not allowed to perform changes." );
}
currentTransaction = new SecurityTransaction( user, registry, adminRole );
registry.beginTransaction( currentTransaction );
return currentTransaction;
}
/**
* Private constructor to enforce the singleton pattern.
* @param registry
* @param timeout
* @throws GeneralSecurityException
*/
private SecurityAccessManager( SecurityRegistry registry, long timeout )
throws GeneralSecurityException {
this.registry = registry;
this.timeout = timeout;
adminUser = getUserByName( "SEC_ADMIN" );
SecurityAccess access = new SecurityAccess( adminUser, registry );
// TODO adminGroup will never been read; can be removed?
//adminGroup = access.getGroupByName( "SEC_ADMIN" );
adminRole = registry.getRoleByName( access, "SEC_ADMIN" );
}
/**
* Verifies that the submitted <code>Transaction</code> is valid. There are two ways for it to
* become invalid:
* <ul>
* <li>it is too old (and timed out)
* <li>it ended (has been aborted / committed)
* </ul>
*
* @param transaction
* @throws ReadWriteLockInvalidException
* @throws GeneralSecurityException
* if transaction is invalid
*/
void verify( SecurityTransaction transaction )
throws ReadWriteLockInvalidException {
if ( transaction == null || transaction != currentTransaction ) {
throw new ReadWriteLockInvalidException( "The SecurityTransaction is invalid." );
} else if ( System.currentTimeMillis() > currentTransaction.getTimestamp() + timeout ) {
currentTransaction = null;
try {
registry.abortTransaction( transaction );
} catch ( GeneralSecurityException e ) {
e.printStackTrace();
}
LOG.logInfo( "timeout: " + timeout );
LOG.logInfo( "current: " + System.currentTimeMillis() );
LOG.logInfo( "lock ts: " + currentTransaction.getTimestamp() );
throw new ReadWriteLockInvalidException( "The SecurityTransaction timed out." );
}
currentTransaction.renew();
}
/**
* Ends the current transaction and commits all changes to the <code>Registry</code>.
* @param transaction
*
* @throws GeneralSecurityException
*/
public void commitTransaction( SecurityTransaction transaction )
throws GeneralSecurityException {
verify( transaction );
currentTransaction = null;
registry.commitTransaction( transaction );
}
/**
* Aborts the current transaction and undoes all changes made to the <code>Registry</code>.
* @param lock
*
* @throws GeneralSecurityException
*/
public void abortTransaction( SecurityTransaction lock )
throws GeneralSecurityException {
verify( lock );
currentTransaction = null;
registry.abortTransaction( lock );
}
}/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: SecurityAccessManager.java,v $
Revision 1.10 2006/08/02 14:11:23 poth
TODO for not used variable set
Revision 1.9 2006/07/12 14:46:18 poth
comment footer added
********************************************************************** */