Commit 94e68b6b authored by Brian Major's avatar Brian Major
Browse files

s1890 - Allowed for the AC web service to be in READONLY and OFFLINE modes

parent 09fc6d35
......@@ -100,6 +100,13 @@ public class LdapConnectionPool
{
private static final Logger logger = Logger.getLogger(LdapConnectionPool.class);
private enum SystemState
{
ONLINE,
READONLY,
OFFLINE
};
Profiler profiler = new Profiler(LdapConnectionPool.class);
protected LdapConfig currentConfig;
......@@ -107,8 +114,10 @@ public class LdapConnectionPool
private LDAPConnectionPool pool;
private Object poolMonitor = new Object();
private LDAPConnectionOptions connectionOptions;
private boolean readOnly;
SystemState systemState = SystemState.ONLINE;
public LdapConnectionPool(LdapConfig config, LdapPool poolConfig, String poolName, boolean boundPool)
public LdapConnectionPool(LdapConfig config, LdapPool poolConfig, String poolName, boolean boundPool, boolean readOnly)
{
if (config == null)
throw new IllegalArgumentException("config required");
......@@ -122,19 +131,49 @@ public class LdapConnectionPool
connectionOptions.setAutoReconnect(true);
currentConfig = config;
this.poolName = poolName;
synchronized (poolMonitor)
this.readOnly = readOnly;
systemState = getSystemState(config);
logger.debug("Construct pool: " + poolName + ". system state: " + systemState);
if (SystemState.ONLINE.equals(systemState) || (SystemState.READONLY.equals(systemState) && readOnly))
{
if (!boundPool)
pool = createPool(config, poolConfig, poolName, null, null);
else
pool = createPool(config, poolConfig, poolName, config.getAdminUserDN(), config.getAdminPasswd());
logger.debug(poolName + " statistics after create:\n" + pool.getConnectionPoolStatistics());
profiler.checkpoint("Create read only pool.");
synchronized (poolMonitor)
{
if (!boundPool)
pool = createPool(config, poolConfig, poolName, null, null);
else
pool = createPool(config, poolConfig, poolName, config.getAdminUserDN(), config.getAdminPasswd());
if (pool != null)
{
logger.debug(poolName + " statistics after create:\n" + pool.getConnectionPoolStatistics());
profiler.checkpoint("Create read only pool.");
}
}
}
else
{
logger.debug("Not creating pool " + poolName + " because system state is " + systemState);
}
}
public LDAPConnection getConnection() throws TransientException
{
logger.debug("Get connection: " + poolName + ". system state: " + systemState);
if (SystemState.OFFLINE.equals(systemState))
{
throw new TransientException("The system is down for maintenance.", 600);
}
if (SystemState.READONLY.equals(systemState))
{
if (!readOnly)
{
throw new TransientException("The system is in read-only mode.", 600);
}
}
try
{
LDAPConnection conn = null;
......@@ -169,8 +208,11 @@ public class LdapConnectionPool
public void releaseConnection(LDAPConnection conn)
{
pool.releaseConnection(conn);
logger.debug(poolName + " pool statistics after release:\n" + pool.getConnectionPoolStatistics());
if (pool != null)
{
pool.releaseConnection(conn);
logger.debug(poolName + " pool statistics after release:\n" + pool.getConnectionPoolStatistics());
}
}
public LdapConfig getCurrentConfig()
......@@ -180,9 +222,12 @@ public class LdapConnectionPool
public void shutdown()
{
logger.debug("Closing pool...");
pool.close();
profiler.checkpoint("Pool closed.");
if (pool != null)
{
logger.debug("Closing pool...");
pool.close();
profiler.checkpoint("Pool closed.");
}
}
public String getName()
......@@ -191,7 +236,6 @@ public class LdapConnectionPool
}
private LDAPConnectionPool createPool(LdapConfig config, LdapPool poolConfig, String poolName, String bindID, String bindPW)
{
try
{
......@@ -245,4 +289,32 @@ public class LdapConnectionPool
}
/**
* Check if in read-only or offline mode.
*
* A read max connection size of zero implies offline mode.
* A read-wrtie max connection size of zero implies read-only mode.
*/
private SystemState getSystemState(LdapConfig config)
{
if (config.getReadOnlyPool().getMaxSize() == 0)
{
return SystemState.OFFLINE;
}
if (config.getUnboundReadOnlyPool().getMaxSize() == 0)
{
return SystemState.OFFLINE;
}
if (config.getReadWritePool().getMaxSize() == 0)
{
return SystemState.READONLY;
}
return SystemState.ONLINE;
}
}
......@@ -147,7 +147,7 @@ class LdapConnections
{
if (readOnlyPool == null)
{
readOnlyPool = new LdapConnectionPool(config, config.getReadOnlyPool(), LdapPersistence.POOL_READONLY, true);
readOnlyPool = new LdapConnectionPool(config, config.getReadOnlyPool(), LdapPersistence.POOL_READONLY, true, true);
}
if (manualConfigReadOnlyConn == null)
{
......@@ -186,7 +186,7 @@ class LdapConnections
{
if (readWritePool == null)
{
readWritePool = new LdapConnectionPool(config, config.getReadWritePool(), LdapPersistence.POOL_READWRITE, true);
readWritePool = new LdapConnectionPool(config, config.getReadWritePool(), LdapPersistence.POOL_READWRITE, true, false);
}
if (manualConfigReadWriteConn == null)
{
......@@ -225,7 +225,7 @@ class LdapConnections
{
if (unboundReadOnlyPool == null)
{
unboundReadOnlyPool = new LdapConnectionPool(config, config.getUnboundReadOnlyPool(), LdapPersistence.POOL_UNBOUNDREADONLY, false);
unboundReadOnlyPool = new LdapConnectionPool(config, config.getUnboundReadOnlyPool(), LdapPersistence.POOL_UNBOUNDREADONLY, false, true);
}
if (manualConfigUnboundReadOnlyConn == null)
{
......@@ -317,4 +317,5 @@ class LdapConnections
return config;
}
}
......@@ -70,7 +70,13 @@ package ca.nrc.cadc.ac.server.ldap;
import java.security.AccessControlException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.security.auth.Subject;
import org.apache.log4j.Logger;
......@@ -87,11 +93,6 @@ import ca.nrc.cadc.auth.AuthMethod;
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.DNPrincipal;
import ca.nrc.cadc.net.TransientException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.security.auth.Subject;
public class LdapGroupPersistence<T extends Principal> extends LdapPersistence implements GroupPersistence<T>
{
......@@ -125,7 +126,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
Subject caller = AuthenticationUtil.getCurrentSubject();
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
......@@ -148,7 +149,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
{
Subject callerSubject = AuthenticationUtil.getCurrentSubject();
boolean allowed = isMember(callerSubject, groupName) || isAdmin(callerSubject, groupName);
LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
......@@ -175,7 +176,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
Subject caller = AuthenticationUtil.getCurrentSubject();
User<Principal> owner = getUser(caller);
group.setOwner(owner);
LdapConnections conns = new LdapConnections(this);
try
{
......@@ -194,7 +195,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
AccessControlException
{
Subject callerSubject = AuthenticationUtil.getCurrentSubject();
LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
......@@ -203,7 +204,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
userDAO = new LdapUserDAO<T>(conns);
groupDAO = new LdapGroupDAO<T>(conns, userDAO);
Group g = groupDAO.getGroup(groupName, false);
if (isOwner(callerSubject, g))
if (isOwner(callerSubject, g))
groupDAO.deleteGroup(groupName);
else
throw new AccessControlException("permission denied");
......@@ -220,7 +221,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
{
Subject callerSubject = AuthenticationUtil.getCurrentSubject();
boolean allowed = isAdmin(callerSubject, group.getID());
LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
......@@ -247,27 +248,27 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
}
/**
*
*
* @param role
* @param groupID check membership in a specific group or null to get all groups
* @return
* @throws UserNotFoundException
* @throws GroupNotFoundException
* @throws TransientException
* @throws AccessControlException
* @throws AccessControlException
*/
public Collection<Group> getGroups(Role role, String groupID)
throws UserNotFoundException, GroupNotFoundException,
TransientException, AccessControlException
{
Subject caller = AuthenticationUtil.getCurrentSubject();
LdapConnections conns = new LdapConnections(this);
try
{
LdapUserDAO<T> userDAO = new LdapUserDAO<T>(conns);
LdapGroupDAO<T> groupDAO = new LdapGroupDAO<T>(conns, userDAO);
if ( Role.OWNER.equals(role))
{
DNPrincipal p = getInternalID(caller);
......@@ -316,20 +317,20 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
conns.releaseConnections();
}
}
// GroupMemberships cache created by AuthenticatorImpl
private List<Group> getGroupCache(Subject caller, Role role)
{
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
Set<GroupMemberships> gset = caller.getPrivateCredentials(GroupMemberships.class);
if (gset == null || gset.isEmpty())
throw new RuntimeException("BUG: no GroupMemberships cache in Subject");
GroupMemberships gms = gset.iterator().next();
return gms.getMemberships(role);
}
// true if the current subject is a member: using GroupMemberships cache
private boolean isMember(Subject caller, String groupName)
{
......@@ -341,7 +342,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
}
return false;
}
private boolean isAdmin(Subject caller, String groupName)
{
List<Group> groups = getGroupCache(caller, Role.ADMIN);
......@@ -352,12 +353,12 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
}
return false;
}
private boolean isOwner(Subject caller, Group g)
{
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
// check owner
for (Principal pc : caller.getPrincipals())
{
......@@ -374,18 +375,18 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
{
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
Set<DNPrincipal> ds = caller.getPrincipals(DNPrincipal.class);
if (ds.isEmpty())
return null;
return ds.iterator().next();
}
private User<Principal> getUser(Subject caller)
{
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
Set<GroupMemberships> gset = caller.getPrivateCredentials(GroupMemberships.class);
if (gset == null || gset.isEmpty())
throw new RuntimeException("BUG: no GroupMemberships cache in Subject");
......
......@@ -115,7 +115,7 @@ public abstract class LdapPersistence
ConnectionPools pools = lookupPools();
if (pools == null || pools.isClosed())
throw new IllegalStateException("Pools are closed.");
poolCheck(pools);
pools = poolCheck(pools);
return pools.getPools().get(poolName);
}
catch (NamingException e)
......@@ -240,11 +240,11 @@ public abstract class LdapPersistence
{
Map<String,LdapConnectionPool> poolMap = new HashMap<String,LdapConnectionPool>(3);
poolMap.put(POOL_READONLY, new LdapConnectionPool(
config, config.getReadOnlyPool(), POOL_READONLY, true));
config, config.getReadOnlyPool(), POOL_READONLY, true, true));
poolMap.put(POOL_READWRITE, new LdapConnectionPool(
config, config.getReadWritePool(), POOL_READWRITE, true));
config, config.getReadWritePool(), POOL_READWRITE, true, false));
poolMap.put(POOL_UNBOUNDREADONLY, new LdapConnectionPool(
config, config.getUnboundReadOnlyPool(), POOL_UNBOUNDREADONLY, false));
config, config.getUnboundReadOnlyPool(), POOL_UNBOUNDREADONLY, false, true));
profiler.checkpoint("Created 3 LDAP connection pools");
return new ConnectionPools(poolMap, config);
}
......@@ -262,7 +262,7 @@ public abstract class LdapPersistence
}
}
private void poolCheck(ConnectionPools pools) throws TransientException
private ConnectionPools poolCheck(ConnectionPools pools) throws TransientException
{
if (timeToCheckPools(pools))
{
......@@ -278,6 +278,7 @@ public abstract class LdapPersistence
logger.debug("Detected ldap configuration change, rebuilding pools");
boolean poolRecreated = false;
final ConnectionPools oldPools = pools;
ConnectionPools newPools = null;
synchronized (jndiMonitor)
{
......@@ -287,7 +288,7 @@ public abstract class LdapPersistence
{
try
{
ConnectionPools newPools = createPools(newConfig);
newPools = createPools(newConfig);
newPools.setLastPoolCheck(System.currentTimeMillis());
InitialContext ic = new InitialContext();
try
......@@ -326,9 +327,13 @@ public abstract class LdapPersistence
};
Thread closePoolsThread = new Thread(closeOldPools);
closePoolsThread.start();
return newPools;
}
}
}
// just return the existing pools
return pools;
}
private boolean timeToCheckPools(ConnectionPools pools)
......
......@@ -72,6 +72,8 @@ import java.security.AccessControlException;
import java.security.Principal;
import java.util.Collection;
import javax.security.auth.Subject;
import org.apache.log4j.Logger;
import ca.nrc.cadc.ac.User;
......@@ -85,8 +87,6 @@ import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.net.TransientException;
import ca.nrc.cadc.profiler.Profiler;
import javax.security.auth.Subject;
public class LdapUserPersistence<T extends Principal> extends LdapPersistence implements UserPersistence<T>
{
private static final Logger logger = Logger.getLogger(LdapUserPersistence.class);
......@@ -173,7 +173,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if ( !isMatch(caller, userID) )
throw new AccessControlException("permission denied: target user does not match current user");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
......@@ -186,7 +186,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
conns.releaseConnections();
}
}
/**
* Get the user specified by email address exists in the active users tree.
*
......@@ -200,7 +200,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
* @throws UserAlreadyExistsException A user with the same email address already exists
*/
public User<Principal> getUserByEmailAddress(String emailAddress)
throws UserNotFoundException, TransientException,
throws UserNotFoundException, TransientException,
AccessControlException, UserAlreadyExistsException
{
LdapConnections conns = new LdapConnections(this);
......@@ -230,7 +230,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if ( !isMatch(caller, userID) )
throw new AccessControlException("permission denied: target user does not match current user");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
......@@ -289,7 +289,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
......@@ -375,7 +375,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if ( !isMatch(caller, user) )
throw new AccessControlException("permission denied: target user does not match current user");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
......@@ -405,7 +405,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if ( !isMatch(caller, userID) )
throw new AccessControlException("permission denied: target user does not match current user");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
......@@ -489,7 +489,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if ( !isMatch(caller, userID) )
throw new AccessControlException("permission denied: target user does not match current user");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
......@@ -523,14 +523,14 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
Subject caller = AuthenticationUtil.getCurrentSubject();
if ( !isMatch(caller, userID) )
throw new AccessControlException("permission denied: target user does not match current user");
LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this);
try
{
userDAO = new LdapUserDAO<T>(conns);
User<T> user = getUser((T) userID);
if (user != null)
{
// oldPassword is correct
......@@ -547,7 +547,7 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
{
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
for (Principal pc : caller.getPrincipals())
{
for (Principal pu : user.getIdentities())
......@@ -558,12 +558,12 @@ public class LdapUserPersistence<T extends Principal> extends LdapPersistence im
}
return false;
}
private boolean isMatch(Subject caller, Principal userID)
{
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated");
for (Principal pc : caller.getPrincipals())
{
if (AuthenticationUtil.equals(pc, userID))
......
......@@ -68,6 +68,24 @@
*/
package ca.nrc.cadc.ac.server.web;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import javax.security.auth.Subject;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import ca.nrc.cadc.ac.Group;
import ca.nrc.cadc.ac.GroupNotFoundException;
import ca.nrc.cadc.ac.UserNotFoundException;
......@@ -75,8 +93,6 @@ import ca.nrc.cadc.ac.server.GroupPersistence;
import ca.nrc.cadc.ac.server.PluginFactory;
import ca.nrc.cadc.ac.server.RequestValidator;
import ca.nrc.cadc.ac.xml.GroupListWriter;
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.net.TransientException;
import ca.nrc.cadc.uws.ExecutionPhase;
import ca.nrc.cadc.uws.Job;
......@@ -84,23 +100,6 @@ import ca.nrc.cadc.uws.server.JobRunner;
import ca.nrc.cadc.uws.server.JobUpdater;