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 ...@@ -100,6 +100,13 @@ public class LdapConnectionPool
{ {
private static final Logger logger = Logger.getLogger(LdapConnectionPool.class); private static final Logger logger = Logger.getLogger(LdapConnectionPool.class);
private enum SystemState
{
ONLINE,
READONLY,
OFFLINE
};
Profiler profiler = new Profiler(LdapConnectionPool.class); Profiler profiler = new Profiler(LdapConnectionPool.class);
protected LdapConfig currentConfig; protected LdapConfig currentConfig;
...@@ -107,8 +114,10 @@ public class LdapConnectionPool ...@@ -107,8 +114,10 @@ public class LdapConnectionPool
private LDAPConnectionPool pool; private LDAPConnectionPool pool;
private Object poolMonitor = new Object(); private Object poolMonitor = new Object();
private LDAPConnectionOptions connectionOptions; 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) if (config == null)
throw new IllegalArgumentException("config required"); throw new IllegalArgumentException("config required");
...@@ -122,19 +131,49 @@ public class LdapConnectionPool ...@@ -122,19 +131,49 @@ public class LdapConnectionPool
connectionOptions.setAutoReconnect(true); connectionOptions.setAutoReconnect(true);
currentConfig = config; currentConfig = config;
this.poolName = poolName; 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) synchronized (poolMonitor)
pool = createPool(config, poolConfig, poolName, null, null); {
else if (!boundPool)
pool = createPool(config, poolConfig, poolName, config.getAdminUserDN(), config.getAdminPasswd()); pool = createPool(config, poolConfig, poolName, null, null);
logger.debug(poolName + " statistics after create:\n" + pool.getConnectionPoolStatistics()); else
profiler.checkpoint("Create read only pool."); 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 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 try
{ {
LDAPConnection conn = null; LDAPConnection conn = null;
...@@ -169,8 +208,11 @@ public class LdapConnectionPool ...@@ -169,8 +208,11 @@ public class LdapConnectionPool
public void releaseConnection(LDAPConnection conn) public void releaseConnection(LDAPConnection conn)
{ {
pool.releaseConnection(conn); if (pool != null)
logger.debug(poolName + " pool statistics after release:\n" + pool.getConnectionPoolStatistics()); {
pool.releaseConnection(conn);
logger.debug(poolName + " pool statistics after release:\n" + pool.getConnectionPoolStatistics());
}
} }
public LdapConfig getCurrentConfig() public LdapConfig getCurrentConfig()
...@@ -180,9 +222,12 @@ public class LdapConnectionPool ...@@ -180,9 +222,12 @@ public class LdapConnectionPool
public void shutdown() public void shutdown()
{ {
logger.debug("Closing pool..."); if (pool != null)
pool.close(); {
profiler.checkpoint("Pool closed."); logger.debug("Closing pool...");
pool.close();
profiler.checkpoint("Pool closed.");
}
} }
public String getName() public String getName()
...@@ -191,7 +236,6 @@ public class LdapConnectionPool ...@@ -191,7 +236,6 @@ public class LdapConnectionPool
} }
private LDAPConnectionPool createPool(LdapConfig config, LdapPool poolConfig, String poolName, String bindID, String bindPW) private LDAPConnectionPool createPool(LdapConfig config, LdapPool poolConfig, String poolName, String bindID, String bindPW)
{ {
try try
{ {
...@@ -245,4 +289,32 @@ public class LdapConnectionPool ...@@ -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 ...@@ -147,7 +147,7 @@ class LdapConnections
{ {
if (readOnlyPool == null) 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) if (manualConfigReadOnlyConn == null)
{ {
...@@ -186,7 +186,7 @@ class LdapConnections ...@@ -186,7 +186,7 @@ class LdapConnections
{ {
if (readWritePool == null) 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) if (manualConfigReadWriteConn == null)
{ {
...@@ -225,7 +225,7 @@ class LdapConnections ...@@ -225,7 +225,7 @@ class LdapConnections
{ {
if (unboundReadOnlyPool == null) 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) if (manualConfigUnboundReadOnlyConn == null)
{ {
...@@ -317,4 +317,5 @@ class LdapConnections ...@@ -317,4 +317,5 @@ class LdapConnections
return config; return config;
} }
} }
...@@ -70,7 +70,13 @@ package ca.nrc.cadc.ac.server.ldap; ...@@ -70,7 +70,13 @@ package ca.nrc.cadc.ac.server.ldap;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.security.Principal; import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection; 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; import org.apache.log4j.Logger;
...@@ -87,11 +93,6 @@ import ca.nrc.cadc.auth.AuthMethod; ...@@ -87,11 +93,6 @@ import ca.nrc.cadc.auth.AuthMethod;
import ca.nrc.cadc.auth.AuthenticationUtil; import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.DNPrincipal; import ca.nrc.cadc.auth.DNPrincipal;
import ca.nrc.cadc.net.TransientException; 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> public class LdapGroupPersistence<T extends Principal> extends LdapPersistence implements GroupPersistence<T>
{ {
...@@ -125,7 +126,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -125,7 +126,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
Subject caller = AuthenticationUtil.getCurrentSubject(); Subject caller = AuthenticationUtil.getCurrentSubject();
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller))) if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated"); throw new AccessControlException("Caller is not authenticated");
LdapGroupDAO<T> groupDAO = null; LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null; LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this); LdapConnections conns = new LdapConnections(this);
...@@ -148,7 +149,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -148,7 +149,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
{ {
Subject callerSubject = AuthenticationUtil.getCurrentSubject(); Subject callerSubject = AuthenticationUtil.getCurrentSubject();
boolean allowed = isMember(callerSubject, groupName) || isAdmin(callerSubject, groupName); boolean allowed = isMember(callerSubject, groupName) || isAdmin(callerSubject, groupName);
LdapGroupDAO<T> groupDAO = null; LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null; LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this); LdapConnections conns = new LdapConnections(this);
...@@ -175,7 +176,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -175,7 +176,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
Subject caller = AuthenticationUtil.getCurrentSubject(); Subject caller = AuthenticationUtil.getCurrentSubject();
User<Principal> owner = getUser(caller); User<Principal> owner = getUser(caller);
group.setOwner(owner); group.setOwner(owner);
LdapConnections conns = new LdapConnections(this); LdapConnections conns = new LdapConnections(this);
try try
{ {
...@@ -194,7 +195,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -194,7 +195,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
AccessControlException AccessControlException
{ {
Subject callerSubject = AuthenticationUtil.getCurrentSubject(); Subject callerSubject = AuthenticationUtil.getCurrentSubject();
LdapGroupDAO<T> groupDAO = null; LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null; LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this); LdapConnections conns = new LdapConnections(this);
...@@ -203,7 +204,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -203,7 +204,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
userDAO = new LdapUserDAO<T>(conns); userDAO = new LdapUserDAO<T>(conns);
groupDAO = new LdapGroupDAO<T>(conns, userDAO); groupDAO = new LdapGroupDAO<T>(conns, userDAO);
Group g = groupDAO.getGroup(groupName, false); Group g = groupDAO.getGroup(groupName, false);
if (isOwner(callerSubject, g)) if (isOwner(callerSubject, g))
groupDAO.deleteGroup(groupName); groupDAO.deleteGroup(groupName);
else else
throw new AccessControlException("permission denied"); throw new AccessControlException("permission denied");
...@@ -220,7 +221,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -220,7 +221,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
{ {
Subject callerSubject = AuthenticationUtil.getCurrentSubject(); Subject callerSubject = AuthenticationUtil.getCurrentSubject();
boolean allowed = isAdmin(callerSubject, group.getID()); boolean allowed = isAdmin(callerSubject, group.getID());
LdapGroupDAO<T> groupDAO = null; LdapGroupDAO<T> groupDAO = null;
LdapUserDAO<T> userDAO = null; LdapUserDAO<T> userDAO = null;
LdapConnections conns = new LdapConnections(this); LdapConnections conns = new LdapConnections(this);
...@@ -247,27 +248,27 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -247,27 +248,27 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
} }
/** /**
* *
* @param role * @param role
* @param groupID check membership in a specific group or null to get all groups * @param groupID check membership in a specific group or null to get all groups
* @return * @return
* @throws UserNotFoundException * @throws UserNotFoundException
* @throws GroupNotFoundException * @throws GroupNotFoundException
* @throws TransientException * @throws TransientException
* @throws AccessControlException * @throws AccessControlException
*/ */
public Collection<Group> getGroups(Role role, String groupID) public Collection<Group> getGroups(Role role, String groupID)
throws UserNotFoundException, GroupNotFoundException, throws UserNotFoundException, GroupNotFoundException,
TransientException, AccessControlException TransientException, AccessControlException
{ {
Subject caller = AuthenticationUtil.getCurrentSubject(); Subject caller = AuthenticationUtil.getCurrentSubject();
LdapConnections conns = new LdapConnections(this); LdapConnections conns = new LdapConnections(this);
try try
{ {
LdapUserDAO<T> userDAO = new LdapUserDAO<T>(conns); LdapUserDAO<T> userDAO = new LdapUserDAO<T>(conns);
LdapGroupDAO<T> groupDAO = new LdapGroupDAO<T>(conns, userDAO); LdapGroupDAO<T> groupDAO = new LdapGroupDAO<T>(conns, userDAO);
if ( Role.OWNER.equals(role)) if ( Role.OWNER.equals(role))
{ {
DNPrincipal p = getInternalID(caller); DNPrincipal p = getInternalID(caller);
...@@ -316,20 +317,20 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -316,20 +317,20 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
conns.releaseConnections(); conns.releaseConnections();
} }
} }
// GroupMemberships cache created by AuthenticatorImpl // GroupMemberships cache created by AuthenticatorImpl
private List<Group> getGroupCache(Subject caller, Role role) private List<Group> getGroupCache(Subject caller, Role role)
{ {
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller))) if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated"); throw new AccessControlException("Caller is not authenticated");
Set<GroupMemberships> gset = caller.getPrivateCredentials(GroupMemberships.class); Set<GroupMemberships> gset = caller.getPrivateCredentials(GroupMemberships.class);
if (gset == null || gset.isEmpty()) if (gset == null || gset.isEmpty())
throw new RuntimeException("BUG: no GroupMemberships cache in Subject"); throw new RuntimeException("BUG: no GroupMemberships cache in Subject");
GroupMemberships gms = gset.iterator().next(); GroupMemberships gms = gset.iterator().next();
return gms.getMemberships(role); return gms.getMemberships(role);
} }
// true if the current subject is a member: using GroupMemberships cache // true if the current subject is a member: using GroupMemberships cache
private boolean isMember(Subject caller, String groupName) private boolean isMember(Subject caller, String groupName)
{ {
...@@ -341,7 +342,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -341,7 +342,7 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
} }
return false; return false;
} }
private boolean isAdmin(Subject caller, String groupName) private boolean isAdmin(Subject caller, String groupName)
{ {
List<Group> groups = getGroupCache(caller, Role.ADMIN); List<Group> groups = getGroupCache(caller, Role.ADMIN);
...@@ -352,12 +353,12 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -352,12 +353,12 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
} }
return false; return false;
} }
private boolean isOwner(Subject caller, Group g) private boolean isOwner(Subject caller, Group g)
{ {
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller))) if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated"); throw new AccessControlException("Caller is not authenticated");
// check owner // check owner
for (Principal pc : caller.getPrincipals()) for (Principal pc : caller.getPrincipals())
{ {
...@@ -374,18 +375,18 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i ...@@ -374,18 +375,18 @@ public class LdapGroupPersistence<T extends Principal> extends LdapPersistence i
{ {
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller))) if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated"); throw new AccessControlException("Caller is not authenticated");
Set<DNPrincipal> ds = caller.getPrincipals(DNPrincipal.class); Set<DNPrincipal> ds = caller.getPrincipals(DNPrincipal.class);
if (ds.isEmpty()) if (ds.isEmpty())
return null; return null;
return ds.iterator().next(); return ds.iterator().next();
} }
private User<Principal> getUser(Subject caller) private User<Principal> getUser(Subject caller)
{ {
if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller))) if (caller == null || AuthMethod.ANON.equals(AuthenticationUtil.getAuthMethod(caller)))
throw new AccessControlException("Caller is not authenticated"); throw new AccessControlException("Caller is not authenticated");
Set<GroupMemberships> gset = caller.getPrivateCredentials(GroupMemberships.class); Set<GroupMemberships> gset = caller.getPrivateCredentials(GroupMemberships.class);
if (gset == null || gset.isEmpty()) if (gset == null || gset.isEmpty())
throw new RuntimeException("BUG: no GroupMemberships cache in Subject"); throw new RuntimeException("BUG: no GroupMemberships cache in Subject");
......
...@@ -115,7 +115,7 @@ public abstract class LdapPersistence ...@@ -115,7 +115,7 @@ public abstract class LdapPersistence
ConnectionPools pools = lookupPools(); ConnectionPools pools = lookupPools();
if (pools == null || pools.isClosed()) if (pools == null || pools.isClosed())
throw new IllegalStateException("Pools are closed."); throw new IllegalStateException("Pools are closed.");
poolCheck(pools); pools = poolCheck(pools);
return pools.getPools().get(poolName); return pools.getPools().get(poolName);
} }
catch (NamingException e) catch (NamingException e)
...@@ -240,11 +240,11 @@ public abstract class LdapPersistence ...@@ -240,11 +240,11 @@ public abstract class LdapPersistence
{ {
Map<String,LdapConnectionPool> poolMap = new HashMap<String,LdapConnectionPool>(3); Map<String,LdapConnectionPool> poolMap = new HashMap<String,LdapConnectionPool>(3);
poolMap.put(POOL_READONLY, new LdapConnectionPool( 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( 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( 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"); profiler.checkpoint("Created 3 LDAP connection pools");
return new ConnectionPools(poolMap, config); return new ConnectionPools(poolMap, config);
} }
...@@ -262,7 +262,7 @@ public abstract class LdapPersistence ...@@ -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)) if (timeToCheckPools(pools))
{ {
...@@ -278,6 +278,7 @@ public abstract class LdapPersistence ...@@ -278,6 +278,7 @@ public abstract class LdapPersistence
logger.debug("Detected ldap configuration change, rebuilding pools"); logger.debug("Detected ldap configuration change, rebuilding pools");
boolean poolRecreated = false; boolean poolRecreated = false;
final ConnectionPools oldPools = pools; final ConnectionPools oldPools = pools;
ConnectionPools newPools = null;
synchronized (jndiMonitor) synchronized (jndiMonitor)
{ {
...@@ -287,7 +288,7 @@ public abstract class LdapPersistence ...@@ -287,7 +288,7 @@ public abstract class LdapPersistence