diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionMap.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionMap.java index 7910a40a0..f8de365ad 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionMap.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/ActiveConnectionMap.java @@ -112,6 +112,76 @@ public class ActiveConnectionMap { this.currentUserCount = 0; } } + + /* + * 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. @@ -120,11 +190,160 @@ public class ActiveConnectionMap { 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. */ private Map activeConnectionMap = new HashMap(); + + /** + * Map of all the connection group users to the count of current usages. + */ + private Map activeConnectionGroupUserMap = + new HashMap(); + + /** + * Map of all the connection users to the count of current usages. + */ + private Map activeConnectionUserMap = + new HashMap(); + + /** + * 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 @@ -232,9 +451,11 @@ public class ActiveConnectionMap { * Set a connection as open. * @param connectionID The ID of the connection that is being opened. * @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. */ - public int openConnection(int connectionID, int userID) { + public int openConnection(int connectionID, int userID, Integer connectionGroupID) { // Create the connection history record ConnectionHistory connectionHistory = new ConnectionHistory(); @@ -245,6 +466,13 @@ public class ActiveConnectionMap { // Increment the user count 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(); } @@ -252,11 +480,14 @@ public class ActiveConnectionMap { /** * Set a connection as closed. * @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 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. */ - public void closeConnection(int connectionID, int historyID) - throws GuacamoleException { + public void closeConnection(int connectionID, int userID, int historyID, + Integer connectionGroupID) throws GuacamoleException { // Get the existing history record ConnectionHistory connectionHistory = @@ -271,5 +502,12 @@ public class ActiveConnectionMap { // Decrement the user count. 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); } } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java index 04bf10d6d..a51d2536e 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLConnection.java @@ -145,7 +145,7 @@ public class MySQLConnection extends AbstractConnection { @Override public GuacamoleSocket connect(GuacamoleClientInformation info) throws GuacamoleException { - return connectionService.connect(this, info, userID); + return connectionService.connect(this, info, userID, null); } @Override diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java index f63a4a030..df81c6876 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLGuacamoleSocket.java @@ -66,25 +66,42 @@ public class MySQLGuacamoleSocket implements GuacamoleSocket { */ 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 * connection. */ 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. * * @param socket The ConfiguredGuacamoleSocket to wrap. * @param connectionID The ID of the connection associated with the given * 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 * 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.connectionID = connectionID; + this.userID = userID; this.historyID = historyID; + this.connectionGroupID = connectionGroupID; } @Override @@ -104,7 +121,8 @@ public class MySQLGuacamoleSocket implements GuacamoleSocket { socket.close(); // Mark this connection as inactive - activeConnectionSet.closeConnection(connectionID, historyID); + activeConnectionSet.closeConnection(connectionID, userID, + historyID, connectionGroupID); } @Override diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java index 10ca5525f..873e46da2 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/properties/MySQLGuacamoleProperties.java @@ -109,4 +109,16 @@ public class MySQLGuacamoleProperties { 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"; } + + }; + + } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java index 56cb98bbe..f6e9a26c3 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionGroupService.java @@ -218,12 +218,18 @@ public class ConnectionGroupService { throw new GuacamoleClientException ("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 MySQLConnection connection = connectionService .retrieveConnection(leastUsedConnectionID, userID); // Connect to the connection - return connectionService.connect(connection, info, userID); + return connectionService.connect(connection, info, userID, group.getConnectionGroupID()); } /** diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java index d639cb4aa..8be28477f 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ConnectionService.java @@ -316,6 +316,8 @@ public class ConnectionService { return connectionRecords; } + + /** * Create a MySQLGuacamoleSocket using the provided connection. @@ -324,12 +326,14 @@ public class ConnectionService { * @param info The information to use when performing the connection * handshake. * @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. * @throws GuacamoleException If an error occurs while connecting the * socket. */ public MySQLGuacamoleSocket connect(MySQLConnection connection, - GuacamoleClientInformation info, int userID) + GuacamoleClientInformation info, int userID, Integer connectionGroupID) throws GuacamoleException { // If the given connection is active, and multiple simultaneous @@ -338,6 +342,12 @@ public class ConnectionService { MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false) && activeConnectionMap.isActive(connection.getConnectionID())) 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 String host = GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_HOSTNAME); @@ -357,11 +367,14 @@ public class ConnectionService { ); // Mark this connection as active - int historyID = activeConnectionMap.openConnection(connection.getConnectionID(), userID); + int historyID = activeConnectionMap.openConnection(connection.getConnectionID(), + userID, connectionGroupID); // Return new MySQLGuacamoleSocket MySQLGuacamoleSocket mySQLGuacamoleSocket = mySQLGuacamoleSocketProvider.get(); - mySQLGuacamoleSocket.init(socket, connection.getConnectionID(), historyID); + mySQLGuacamoleSocket.init(socket, connection.getConnectionID(), userID, + historyID, connectionGroupID); + return mySQLGuacamoleSocket; }