Ticket #390: Added mysql-disallow-duplicate-connections property and enforced connection and connectionGroup access restrictions thereof.

This commit is contained in:
James Muehlner
2013-08-18 19:32:22 -07:00
parent 94647857ed
commit 681a0201ba
6 changed files with 298 additions and 11 deletions

View File

@@ -113,6 +113,76 @@ public class ActiveConnectionMap {
} }
} }
/*
* Represents a user connected to a connection or BALANCING connection group.
*/
public class ConnectionUser {
/**
* The ID of the connection or connection group that this ConnectionUser refers to.
*/
private int connectionID;
/**
* The user that this ConnectionUser refers to.
*/
private int userID;
/**
* Returns ID of the connection or connection group that this ConnectionUser refers to.
* @return ID of the connection or connection group that this ConnectionUser refers to.
*/
public int getConnectionGroupID() {
return connectionID;
}
/**
* Returns the user ID that this ConnectionUser refers to.
* @return the user ID that this ConnectionUser refers to.
*/
public int getUserID() {
return userID;
}
/**
* Create a ConnectionUser with the given connection or connection group
* ID and user ID.
*
* @param connectionID The connection or connection group ID that this
* ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*/
public ConnectionUser(int connectionID, int userID) {
this.connectionID = connectionID;
this.userID = userID;
}
@Override
public boolean equals(Object other) {
// Only another ConnectionUser can equal this ConnectionUser
if(!(other instanceof ConnectionUser))
return false;
ConnectionUser otherConnectionGroupUser =
(ConnectionUser)other;
/*
* Two ConnectionGroupUsers are equal iff they represent the exact
* same pairing of connection or connection group and user.
*/
return this.connectionID == otherConnectionGroupUser.connectionID
&& this.userID == otherConnectionGroupUser.userID;
}
@Override
public int hashCode() {
int hash = 3;
hash = 23 * hash + this.connectionID;
hash = 23 * hash + this.userID;
return hash;
}
}
/** /**
* DAO for accessing connection history. * DAO for accessing connection history.
*/ */
@@ -120,12 +190,161 @@ public class ActiveConnectionMap {
private ConnectionHistoryMapper connectionHistoryDAO; private ConnectionHistoryMapper connectionHistoryDAO;
/** /**
* Map of all the connections that are currently active the * Map of all the connections that are currently active to the
* count of current users. * count of current users.
*/ */
private Map<Integer, Connection> activeConnectionMap = private Map<Integer, Connection> activeConnectionMap =
new HashMap<Integer, Connection>(); new HashMap<Integer, Connection>();
/**
* Map of all the connection group users to the count of current usages.
*/
private Map<ConnectionUser, Integer> activeConnectionGroupUserMap =
new HashMap<ConnectionUser, Integer>();
/**
* Map of all the connection users to the count of current usages.
*/
private Map<ConnectionUser, Integer> activeConnectionUserMap =
new HashMap<ConnectionUser, Integer>();
/**
* Returns the number of connectionGroups opened by the given user using
* the given ConnectionGroup.
*
* @param connectionID The connection group ID that this
* ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*
* @return The number of connections opened by the given user to the given
* ConnectionGroup.
*/
public int getConnectionGroupUserCount(int connectionGroupID, int userID) {
Integer count = activeConnectionGroupUserMap.get
(new ConnectionUser(connectionGroupID, userID));
// No ConnectionUser found means this combination was never used
if(count == null)
return 0;
return count;
}
/**
* Checks if the given user is currently connected to the given BALANCING
* connection group.
*
* @param connectionGroupID The connection group ID that this
* ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*
* @return True if the given user is currently connected to the given
* BALANCING connection group, false otherwise.
*/
public boolean isConnectionGroupUserActive(int connectionGroupID, int userID) {
Integer count = activeConnectionGroupUserMap.get
(new ConnectionUser(connectionGroupID, userID));
// The connection group is in use if the ConnectionUser count > 0
return count != null && count > 0;
}
/**
* Increment the count of the number of connections opened by the given user
* to the given ConnectionGroup.
*
* @param connectionGroupID The connection group ID that this
* ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*/
private void incrementConnectionGroupUserCount(int connectionGroupID, int userID) {
int currentCount = getConnectionGroupUserCount(connectionGroupID, userID);
activeConnectionGroupUserMap.put
(new ConnectionUser(connectionGroupID, userID), currentCount + 1);
}
/**
* Decrement the count of the number of connections opened by the given user
* to the given ConnectionGroup.
*
* @param connectionGroupID The connection group ID that this
* ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*/
private void decrementConnectionGroupUserCount(int connectionGroupID, int userID) {
int currentCount = getConnectionGroupUserCount(connectionGroupID, userID);
activeConnectionGroupUserMap.put
(new ConnectionUser(connectionGroupID, userID), currentCount - 1);
}
/**
* Returns the number of connections opened by the given user using
* the given Connection.
*
* @param connectionID The connection ID that this ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*
* @return The number of connections opened by the given user to the given
* connection.
*/
public int getConnectionUserCount(int connectionID, int userID) {
Integer count = activeConnectionUserMap.get
(new ConnectionUser(connectionID, userID));
// No ConnectionUser found means this combination was never used
if(count == null)
return 0;
return count;
}
/**
* Checks if the given user is currently connected to the given connection.
*
* @param connectionID The connection ID that this ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*
* @return True if the given user is currently connected to the given
* connection, false otherwise.
*/
public boolean isConnectionUserActive(int connectionID, int userID) {
Integer count = activeConnectionUserMap.get
(new ConnectionUser(connectionID, userID));
// The connection is in use if the ConnectionUser count > 0
return count != null && count > 0;
}
/**
* Increment the count of the number of connections opened by the given user
* to the given Connection.
*
* @param connectionID The connection ID that this ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*/
private void incrementConnectionUserCount(int connectionID, int userID) {
int currentCount = getConnectionGroupUserCount(connectionID, userID);
activeConnectionUserMap.put
(new ConnectionUser(connectionID, userID), currentCount + 1);
}
/**
* Decrement the count of the number of connections opened by the given user
* to the given Connection.
*
* @param connectionID The connection ID that this ConnectionUser refers to.
* @param userID The user ID that this ConnectionUser refers to.
*/
private void decrementConnectionUserCount(int connectionID, int userID) {
int currentCount = getConnectionGroupUserCount(connectionID, userID);
activeConnectionUserMap.put
(new ConnectionUser(connectionID, userID), currentCount - 1);
}
/** /**
* Returns the ID of the connection with the lowest number of current * Returns the ID of the connection with the lowest number of current
* active users, if found. * active users, if found.
@@ -232,9 +451,11 @@ public class ActiveConnectionMap {
* Set a connection as open. * Set a connection as open.
* @param connectionID The ID of the connection that is being opened. * @param connectionID The ID of the connection that is being opened.
* @param userID The ID of the user who is opening the connection. * @param userID The ID of the user who is opening the connection.
* @param connectionID The ID of the BALANCING connection group that is
* being connected to; null if not used.
* @return The ID of the history record created for this open connection. * @return The ID of the history record created for this open connection.
*/ */
public int openConnection(int connectionID, int userID) { public int openConnection(int connectionID, int userID, Integer connectionGroupID) {
// Create the connection history record // Create the connection history record
ConnectionHistory connectionHistory = new ConnectionHistory(); ConnectionHistory connectionHistory = new ConnectionHistory();
@@ -246,17 +467,27 @@ public class ActiveConnectionMap {
// Increment the user count // Increment the user count
incrementUserCount(connectionID); incrementUserCount(connectionID);
// Increment the connection user count
incrementConnectionUserCount(connectionID, userID);
// If this is a connection to a BALANCING ConnectionGroup, increment the count
if(connectionGroupID != null)
incrementConnectionGroupUserCount(connectionGroupID, userID);
return connectionHistory.getHistory_id(); return connectionHistory.getHistory_id();
} }
/** /**
* Set a connection as closed. * Set a connection as closed.
* @param connectionID The ID of the connection that is being opened. * @param connectionID The ID of the connection that is being opened.
* @param userID The ID of the user who is opening the connection.
* @param historyID The ID of the history record about the open connection. * @param historyID The ID of the history record about the open connection.
* @param connectionID The ID of the BALANCING connection group that is
* being connected to; null if not used.
* @throws GuacamoleException If the open connection history is not found. * @throws GuacamoleException If the open connection history is not found.
*/ */
public void closeConnection(int connectionID, int historyID) public void closeConnection(int connectionID, int userID, int historyID,
throws GuacamoleException { Integer connectionGroupID) throws GuacamoleException {
// Get the existing history record // Get the existing history record
ConnectionHistory connectionHistory = ConnectionHistory connectionHistory =
@@ -271,5 +502,12 @@ public class ActiveConnectionMap {
// Decrement the user count. // Decrement the user count.
decrementUserCount(connectionID); decrementUserCount(connectionID);
// Decrement the connection user count
decrementConnectionUserCount(connectionID, userID);
// If this is a connection to a BALANCING ConnectionGroup, decrement the count
if(connectionGroupID != null)
decrementConnectionGroupUserCount(connectionGroupID, userID);
} }
} }

View File

@@ -145,7 +145,7 @@ public class MySQLConnection extends AbstractConnection {
@Override @Override
public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException {
return connectionService.connect(this, info, userID); return connectionService.connect(this, info, userID, null);
} }
@Override @Override

View File

@@ -66,25 +66,42 @@ public class MySQLGuacamoleSocket implements GuacamoleSocket {
*/ */
private int connectionID; private int connectionID;
/**
* The ID of the user who is connecting to the socket.
*/
private int userID;
/** /**
* The ID of the history record associated with this instance of the * The ID of the history record associated with this instance of the
* connection. * connection.
*/ */
private int historyID; private int historyID;
/**
* The ID of the balancing connection group that is being connected to;
* null if not used.
*/
private Integer connectionGroupID;
/** /**
* Initialize this MySQLGuacamoleSocket with the provided GuacamoleSocket. * Initialize this MySQLGuacamoleSocket with the provided GuacamoleSocket.
* *
* @param socket The ConfiguredGuacamoleSocket to wrap. * @param socket The ConfiguredGuacamoleSocket to wrap.
* @param connectionID The ID of the connection associated with the given * @param connectionID The ID of the connection associated with the given
* socket. * socket.
* @param userID The ID of the user who is connecting to the socket.
* @param historyID The ID of the history record associated with this * @param historyID The ID of the history record associated with this
* instance of the connection. * instance of the connection.
* @param connectionGroupID The ID of the balancing connection group that is
* being connected to; null if not used.
*/ */
public void init(GuacamoleSocket socket, int connectionID, int historyID) { public void init(GuacamoleSocket socket, int connectionID, int userID,
int historyID, Integer connectionGroupID) {
this.socket = socket; this.socket = socket;
this.connectionID = connectionID; this.connectionID = connectionID;
this.userID = userID;
this.historyID = historyID; this.historyID = historyID;
this.connectionGroupID = connectionGroupID;
} }
@Override @Override
@@ -104,7 +121,8 @@ public class MySQLGuacamoleSocket implements GuacamoleSocket {
socket.close(); socket.close();
// Mark this connection as inactive // Mark this connection as inactive
activeConnectionSet.closeConnection(connectionID, historyID); activeConnectionSet.closeConnection(connectionID, userID,
historyID, connectionGroupID);
} }
@Override @Override

View File

@@ -109,4 +109,16 @@ public class MySQLGuacamoleProperties {
public String getName() { return "mysql-disallow-simultaneous-connections"; } public String getName() { return "mysql-disallow-simultaneous-connections"; }
}; };
/**
* Whether or not the same user accessing the same connection or connection group at the same time should be disallowed.
*/
public static final BooleanGuacamoleProperty MYSQL_DISALLOW_DUPLICATE_CONNECTIONS = new BooleanGuacamoleProperty() {
@Override
public String getName() { return "mysql-disallow-duplicate-connections"; }
};
} }

View File

@@ -218,12 +218,18 @@ public class ConnectionGroupService {
throw new GuacamoleClientException throw new GuacamoleClientException
("Cannot connect. All connections are in use."); ("Cannot connect. All connections are in use.");
if(GuacamoleProperties.getProperty(
MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true)
&& activeConnectionMap.isConnectionGroupUserActive(group.getConnectionGroupID(), userID))
throw new GuacamoleClientException
("Cannot connect. Connection group already in use by this user.");
// Get the connection // Get the connection
MySQLConnection connection = connectionService MySQLConnection connection = connectionService
.retrieveConnection(leastUsedConnectionID, userID); .retrieveConnection(leastUsedConnectionID, userID);
// Connect to the connection // Connect to the connection
return connectionService.connect(connection, info, userID); return connectionService.connect(connection, info, userID, group.getConnectionGroupID());
} }
/** /**

View File

@@ -317,6 +317,8 @@ public class ConnectionService {
return connectionRecords; return connectionRecords;
} }
/** /**
* Create a MySQLGuacamoleSocket using the provided connection. * Create a MySQLGuacamoleSocket using the provided connection.
* *
@@ -324,12 +326,14 @@ public class ConnectionService {
* @param info The information to use when performing the connection * @param info The information to use when performing the connection
* handshake. * handshake.
* @param userID The ID of the user who is connecting to the socket. * @param userID The ID of the user who is connecting to the socket.
* @param connectionGroupID The ID of the balancing connection group that is
* being connected to; null if not used.
* @return The connected socket. * @return The connected socket.
* @throws GuacamoleException If an error occurs while connecting the * @throws GuacamoleException If an error occurs while connecting the
* socket. * socket.
*/ */
public MySQLGuacamoleSocket connect(MySQLConnection connection, public MySQLGuacamoleSocket connect(MySQLConnection connection,
GuacamoleClientInformation info, int userID) GuacamoleClientInformation info, int userID, Integer connectionGroupID)
throws GuacamoleException { throws GuacamoleException {
// If the given connection is active, and multiple simultaneous // If the given connection is active, and multiple simultaneous
@@ -339,6 +343,12 @@ public class ConnectionService {
&& activeConnectionMap.isActive(connection.getConnectionID())) && activeConnectionMap.isActive(connection.getConnectionID()))
throw new GuacamoleClientException("Cannot connect. This connection is in use."); throw new GuacamoleClientException("Cannot connect. This connection is in use.");
if(GuacamoleProperties.getProperty(
MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true)
&& activeConnectionMap.isConnectionUserActive(connection.getConnectionID(), userID))
throw new GuacamoleClientException
("Cannot connect. Connection already in use by this user.");
// Get guacd connection information // Get guacd connection information
String host = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME); String host = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME);
int port = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_PORT); int port = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_PORT);
@@ -357,11 +367,14 @@ public class ConnectionService {
); );
// Mark this connection as active // Mark this connection as active
int historyID = activeConnectionMap.openConnection(connection.getConnectionID(), userID); int historyID = activeConnectionMap.openConnection(connection.getConnectionID(),
userID, connectionGroupID);
// Return new MySQLGuacamoleSocket // Return new MySQLGuacamoleSocket
MySQLGuacamoleSocket mySQLGuacamoleSocket = mySQLGuacamoleSocketProvider.get(); MySQLGuacamoleSocket mySQLGuacamoleSocket = mySQLGuacamoleSocketProvider.get();
mySQLGuacamoleSocket.init(socket, connection.getConnectionID(), historyID); mySQLGuacamoleSocket.init(socket, connection.getConnectionID(), userID,
historyID, connectionGroupID);
return mySQLGuacamoleSocket; return mySQLGuacamoleSocket;
} }