From 3b94f5d4b3c8c2e9d099c96125b66ea336e3228b Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Thu, 20 Aug 2015 17:57:19 -0700 Subject: [PATCH 1/2] GUAC-830: Add ConfigurableGuacamoleTunnelService. --- .../modules/guacamole-auth-jdbc-base/pom.xml | 9 +- .../ConfigurableGuacamoleTunnelService.java | 276 ++++++++++++++++++ 2 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/pom.xml b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/pom.xml index 69107ba42..1556f21da 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/pom.xml +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/pom.xml @@ -90,7 +90,14 @@ guice-multibindings 3.0 - + + + + com.google.guava + guava + 18.0 + + diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java new file mode 100644 index 000000000..a3f5b6d0e --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/ConfigurableGuacamoleTunnelService.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.jdbc.tunnel; + +import com.google.common.collect.ConcurrentHashMultiset; +import com.google.inject.Singleton; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import org.glyptodon.guacamole.GuacamoleClientTooManyException; +import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; +import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleResourceConflictException; +import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; + + +/** + * GuacamoleTunnelService implementation which restricts concurrency for each + * connection and group according to a maximum number of connections and + * maximum number of connections per user. + * + * @author James Muehlner + * @author Michael Jumper + */ +@Singleton +public class ConfigurableGuacamoleTunnelService + extends AbstractGuacamoleTunnelService { + + /** + * Set of all currently-active user/connection pairs (seats). + */ + private final ConcurrentHashMultiset activeSeats = ConcurrentHashMultiset.create(); + + /** + * Set of all currently-active connections. + */ + private final ConcurrentHashMultiset activeConnections = ConcurrentHashMultiset.create(); + + /** + * Set of all currently-active user/connection group pairs (seats). + */ + private final ConcurrentHashMultiset activeGroupSeats = ConcurrentHashMultiset.create(); + + /** + * Set of all currently-active connection groups. + */ + private final ConcurrentHashMultiset activeGroups = ConcurrentHashMultiset.create(); + + /** + * The maximum number of connections allowed per connection by default, or + * zero if no default limit applies. + */ + private final int connectionDefaultMaxConnections; + + /** + * The maximum number of connections a user may have to any one connection + * by default, or zero if no default limit applies. + */ + private final int connectionDefaultMaxConnectionsPerUser; + + /** + * The maximum number of connections allowed per connection group by + * default, or zero if no default limit applies. + */ + private final int connectionGroupDefaultMaxConnections; + + /** + * The maximum number of connections a user may have to any one connection + * group by default, or zero if no default limit applies. + */ + private final int connectionGroupDefaultMaxConnectionsPerUser; + + /** + * Creates a new ConfigurableGuacamoleTunnelService which applies the given + * limitations when new connections are acquired. + * + * @param connectionDefaultMaxConnections + * The maximum number of connections allowed per connection by default, + * or zero if no default limit applies. + * + * @param connectionDefaultMaxConnectionsPerUser + * The maximum number of connections a user may have to any one + * connection by default, or zero if no default limit applies. + * + * @param connectionGroupDefaultMaxConnections + * The maximum number of connections allowed per connection group by + * default, or zero if no default limit applies. + * + * @param connectionGroupDefaultMaxConnectionsPerUser + * The maximum number of connections a user may have to any one + * connection group by default, or zero if no default limit applies. + */ + public ConfigurableGuacamoleTunnelService( + int connectionDefaultMaxConnections, + int connectionDefaultMaxConnectionsPerUser, + int connectionGroupDefaultMaxConnections, + int connectionGroupDefaultMaxConnectionsPerUser) { + + // Set default connection limits + this.connectionDefaultMaxConnections = connectionDefaultMaxConnections; + this.connectionDefaultMaxConnectionsPerUser = connectionDefaultMaxConnectionsPerUser; + + // Set default connection group limits + this.connectionGroupDefaultMaxConnections = connectionGroupDefaultMaxConnections; + this.connectionGroupDefaultMaxConnectionsPerUser = connectionGroupDefaultMaxConnectionsPerUser; + + } + + /** + * Attempts to add a single instance of the given value to the given + * multiset without exceeding the specified maximum number of values. If + * the value cannot be added without exceeding the maximum, false is + * returned. + * + * @param + * The type of values contained within the multiset. + * + * @param multiset + * The multiset to attempt to add a value to. + * + * @param value + * The value to attempt to add. + * + * @param max + * The maximum number of each distinct value that the given multiset + * should hold, or zero if no limit applies. + * + * @return + * true if the value was successfully added without exceeding the + * specified maximum, false if the value could not be added. + */ + private boolean tryAdd(ConcurrentHashMultiset multiset, T value, int max) { + + // Repeatedly attempt to add a new value to the given multiset until we + // explicitly succeed or explicitly fail + while (true) { + + // Get current number of values + int count = multiset.count(value); + + // Bail out if the maximum has already been reached + if (count >= max || max == 0) + return false; + + // Attempt to add one more value + if (multiset.setCount(value, count, count+1)) + return true; + + // Try again if unsuccessful + + } + + } + + @Override + protected ModeledConnection acquire(AuthenticatedUser user, + List connections) throws GuacamoleException { + + // Get username + String username = user.getUser().getIdentifier(); + + // Sort connections in ascending order of usage + ModeledConnection[] sortedConnections = connections.toArray(new ModeledConnection[connections.size()]); + Arrays.sort(sortedConnections, new Comparator() { + + @Override + public int compare(ModeledConnection a, ModeledConnection b) { + + return getActiveConnections(a).size() + - getActiveConnections(b).size(); + + } + + }); + + // Track whether acquire fails due to user-specific limits + boolean userSpecificFailure = true; + + // Return the first unreserved connection + for (ModeledConnection connection : sortedConnections) { + + // Attempt to aquire connection according to per-user limits + Seat seat = new Seat(username, connection.getIdentifier()); + if (tryAdd(activeSeats, seat, + connectionDefaultMaxConnectionsPerUser)) { + + // Attempt to aquire connection according to overall limits + if (tryAdd(activeConnections, connection.getIdentifier(), + connectionDefaultMaxConnections)) + return connection; + + // Acquire failed - retry with next connection + activeSeats.remove(seat); + + // Failure to acquire is not user-specific + userSpecificFailure = false; + + } + + } + + // Too many connections by this user + if (userSpecificFailure) + throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user."); + + // Too many connections, but not necessarily due purely to this user + else + throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use."); + + } + + @Override + protected void release(AuthenticatedUser user, ModeledConnection connection) { + activeSeats.remove(new Seat(user.getUser().getIdentifier(), connection.getIdentifier())); + activeConnections.remove(connection.getIdentifier()); + } + + @Override + protected void acquire(AuthenticatedUser user, + ModeledConnectionGroup connectionGroup) throws GuacamoleException { + + // Get username + String username = user.getUser().getIdentifier(); + + // Attempt to aquire connection group according to per-user limits + Seat seat = new Seat(username, connectionGroup.getIdentifier()); + if (tryAdd(activeGroupSeats, seat, + connectionGroupDefaultMaxConnectionsPerUser)) { + + // Attempt to aquire connection group according to overall limits + if (tryAdd(activeGroups, connectionGroup.getIdentifier(), + connectionGroupDefaultMaxConnections)) + return; + + // Acquire failed + activeGroupSeats.remove(seat); + + // Failure to acquire is not user-specific + throw new GuacamoleResourceConflictException("Cannot connect. This connection group is in use."); + + } + + // Already in use by this user + throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user."); + + } + + @Override + protected void release(AuthenticatedUser user, + ModeledConnectionGroup connectionGroup) { + activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier())); + activeGroups.remove(connectionGroup.getIdentifier()); + } + +} From 222aa93e41453dacc5db854905d71b7fc85e65d6 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 20 Aug 2015 18:30:05 -0700 Subject: [PATCH 2/2] GUAC-830: Migrate to ConfigurableGuacamoleTunnelService for all values of legacy properties. --- .../JDBCAuthenticationProviderModule.java | 17 ++- .../BalancedGuacamoleTunnelService.java | 88 ------------- .../MultiseatGuacamoleTunnelService.java | 116 ------------------ .../SingleSeatGuacamoleTunnelService.java | 99 --------------- .../UnrestrictedGuacamoleTunnelService.java | 82 ------------- .../mysql/MySQLAuthenticationProvider.java | 71 ++++++----- .../PostgreSQLAuthenticationProvider.java | 71 ++++++----- 7 files changed, 86 insertions(+), 458 deletions(-) delete mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/BalancedGuacamoleTunnelService.java delete mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/MultiseatGuacamoleTunnelService.java delete mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/SingleSeatGuacamoleTunnelService.java delete mode 100644 extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/UnrestrictedGuacamoleTunnelService.java diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java index 017e6c297..bc7580033 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java @@ -82,10 +82,9 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { private final Environment environment; /** - * The service class to use to provide GuacamoleSockets for each - * connection. + * The service to use to provide GuacamoleTunnels for each connection. */ - private final Class tunnelServiceClass; + private final GuacamoleTunnelService tunnelService; /** * Creates a new JDBC authentication provider module that configures the @@ -95,13 +94,13 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { * @param environment * The environment to use to configure injected classes. * - * @param tunnelServiceClass - * The socket service to use to provide sockets for connections. + * @param tunnelService + * The tunnel service to use to provide tunnels sockets for connections. */ public JDBCAuthenticationProviderModule(Environment environment, - Class tunnelServiceClass) { + GuacamoleTunnelService tunnelService) { this.environment = environment; - this.tunnelServiceClass = tunnelServiceClass; + this.tunnelService = tunnelService; } @Override @@ -156,8 +155,8 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { bind(UserPermissionService.class); bind(UserService.class); - // Bind provided socket service - bind(GuacamoleTunnelService.class).to(tunnelServiceClass); + // Bind provided tunnel service + bind(GuacamoleTunnelService.class).toInstance(tunnelService); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/BalancedGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/BalancedGuacamoleTunnelService.java deleted file mode 100644 index b2cfcc8d1..000000000 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/BalancedGuacamoleTunnelService.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2015 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.auth.jdbc.tunnel; - -import com.google.inject.Singleton; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; -import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleResourceConflictException; -import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; - - -/** - * GuacamoleTunnelService implementation which allows only one user per - * connection at any time, but does not disallow concurrent use of connection - * groups. If a user attempts to use a connection group multiple times, they - * will receive different underlying connections each time until the group is - * exhausted. - * - * @author Michael Jumper - */ -@Singleton -public class BalancedGuacamoleTunnelService - extends AbstractGuacamoleTunnelService { - - /** - * The set of all active connection identifiers. - */ - private final Set activeConnections = - Collections.newSetFromMap(new ConcurrentHashMap()); - - @Override - protected ModeledConnection acquire(AuthenticatedUser user, - List connections) throws GuacamoleException { - - // Return the first unused connection - for (ModeledConnection connection : connections) { - if (activeConnections.add(connection.getIdentifier())) - return connection; - } - - // Already in use - throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use."); - - } - - @Override - protected void release(AuthenticatedUser user, ModeledConnection connection) { - activeConnections.remove(connection.getIdentifier()); - } - - @Override - protected void acquire(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) throws GuacamoleException { - // Do nothing - } - - @Override - protected void release(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) { - // Do nothing - } - -} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/MultiseatGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/MultiseatGuacamoleTunnelService.java deleted file mode 100644 index 412e2f32e..000000000 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/MultiseatGuacamoleTunnelService.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2015 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.auth.jdbc.tunnel; - -import com.google.inject.Singleton; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import org.glyptodon.guacamole.GuacamoleClientTooManyException; -import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; -import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleResourceConflictException; -import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; - - -/** - * GuacamoleTunnelService implementation which restricts concurrency only on a - * per-user basis. Each connection or group may be used concurrently any number - * of times, but each concurrent use must be associated with a different user. - * - * @author Michael Jumper - */ -@Singleton -public class MultiseatGuacamoleTunnelService - extends AbstractGuacamoleTunnelService { - - /** - * The set of all active user/connection pairs. - */ - private final Set activeSeats = - Collections.newSetFromMap(new ConcurrentHashMap()); - - /** - * The set of all active user/connection group pairs. - */ - private final Set activeGroupSeats = - Collections.newSetFromMap(new ConcurrentHashMap()); - - @Override - protected ModeledConnection acquire(AuthenticatedUser user, - List connections) throws GuacamoleException { - - String username = user.getUser().getIdentifier(); - - // Sort connections in ascending order of usage - ModeledConnection[] sortedConnections = connections.toArray(new ModeledConnection[connections.size()]); - Arrays.sort(sortedConnections, new Comparator() { - - @Override - public int compare(ModeledConnection a, ModeledConnection b) { - - return getActiveConnections(a).size() - - getActiveConnections(b).size(); - - } - - }); - - // Return the first unreserved connection - for (ModeledConnection connection : sortedConnections) { - if (activeSeats.add(new Seat(username, connection.getIdentifier()))) - return connection; - } - - // Already in use - throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use."); - - } - - @Override - protected void release(AuthenticatedUser user, ModeledConnection connection) { - activeSeats.remove(new Seat(user.getUser().getIdentifier(), connection.getIdentifier())); - } - - @Override - protected void acquire(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) throws GuacamoleException { - - // Do not allow duplicate use of connection groups - Seat seat = new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()); - if (!activeGroupSeats.add(seat)) - throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user."); - - } - - @Override - protected void release(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) { - activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier())); - } - -} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/SingleSeatGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/SingleSeatGuacamoleTunnelService.java deleted file mode 100644 index 9fce08e5f..000000000 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/SingleSeatGuacamoleTunnelService.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2015 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.auth.jdbc.tunnel; - -import com.google.inject.Singleton; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import org.glyptodon.guacamole.GuacamoleClientTooManyException; -import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; -import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleResourceConflictException; -import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; - - -/** - * GuacamoleTunnelService implementation which allows exactly one use - * of any connection at any time. Concurrent usage of connections is not - * allowed, and concurrent usage of connection groups is allowed only between - * different users. - * - * @author Michael Jumper - */ -@Singleton -public class SingleSeatGuacamoleTunnelService - extends AbstractGuacamoleTunnelService { - - /** - * The set of all active connection identifiers. - */ - private final Set activeConnections = - Collections.newSetFromMap(new ConcurrentHashMap()); - - /** - * The set of all active user/connection group pairs. - */ - private final Set activeGroupSeats = - Collections.newSetFromMap(new ConcurrentHashMap()); - - @Override - protected ModeledConnection acquire(AuthenticatedUser user, - List connections) throws GuacamoleException { - - // Return the first unused connection - for (ModeledConnection connection : connections) { - if (activeConnections.add(connection.getIdentifier())) - return connection; - } - - // Already in use - throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use."); - - } - - @Override - protected void release(AuthenticatedUser user, ModeledConnection connection) { - activeConnections.remove(connection.getIdentifier()); - } - - @Override - protected void acquire(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) throws GuacamoleException { - - // Do not allow duplicate use of connection groups - Seat seat = new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier()); - if (!activeGroupSeats.add(seat)) - throw new GuacamoleClientTooManyException("Cannot connect. Connection group already in use by this user."); - - } - - @Override - protected void release(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) { - activeGroupSeats.remove(new Seat(user.getUser().getIdentifier(), connectionGroup.getIdentifier())); - } - -} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/UnrestrictedGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/UnrestrictedGuacamoleTunnelService.java deleted file mode 100644 index 5aad81481..000000000 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/tunnel/UnrestrictedGuacamoleTunnelService.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2015 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.auth.jdbc.tunnel; - -import com.google.inject.Singleton; -import java.util.List; -import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; -import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; - - -/** - * GuacamoleTunnelService implementation which imposes no restrictions - * whatsoever on the number of concurrent or duplicate connections. - * - * @author Michael Jumper - */ -@Singleton -public class UnrestrictedGuacamoleTunnelService - extends AbstractGuacamoleTunnelService { - - @Override - protected ModeledConnection acquire(AuthenticatedUser user, - List connections) throws GuacamoleException { - - ModeledConnection chosen = null; - int lowestUsage = 0; - - // Find connection with lowest usage - for (ModeledConnection connection : connections) { - - int usage = getActiveConnections(connection).size(); - if (chosen == null || usage < lowestUsage) { - chosen = connection; - lowestUsage = usage; - } - - } - - return chosen; - - } - - @Override - protected void release(AuthenticatedUser user, ModeledConnection connection) { - // Do nothing - } - - @Override - protected void acquire(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) throws GuacamoleException { - // Do nothing - } - - @Override - protected void release(AuthenticatedUser user, - ModeledConnectionGroup connectionGroup) { - // Do nothing - } - -} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java index 617061bc2..544365a95 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java @@ -30,10 +30,7 @@ import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule; import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.MultiseatGuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.BalancedGuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.SingleSeatGuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.UnrestrictedGuacamoleTunnelService; +import org.glyptodon.guacamole.auth.jdbc.tunnel.ConfigurableGuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.user.UserContextService; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.LocalEnvironment; @@ -54,52 +51,62 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider { private final Injector injector; /** - * Returns the appropriate socket service class given the Guacamole - * environment. The class is chosen based on configuration options that - * dictate concurrent usage policy. + * Returns the appropriate tunnel service given the Guacamole environment. + * The service is configured based on configuration options that dictate + * the default concurrent usage policy. * * @param environment * The environment of the Guacamole server. * * @return - * The socket service class that matches the concurrent usage policy - * options set in the Guacamole environment. + * A tunnel service implementation configured according to the + * concurrent usage policy options set in the Guacamole environment. * * @throws GuacamoleException * If an error occurs while reading the configuration options. */ - private Class - getSocketServiceClass(Environment environment) + private GuacamoleTunnelService getTunnelService(Environment environment) throws GuacamoleException { - // Read concurrency-related properties + // Tunnel service default configuration + int connectionDefaultMaxConnections; + int connectionDefaultMaxConnectionsPerUser; + int connectionGroupDefaultMaxConnections; + int connectionGroupDefaultMaxConnectionsPerUser; + + // Read legacy concurrency-related properties boolean disallowSimultaneous = environment.getProperty(MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false); boolean disallowDuplicate = environment.getProperty(MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true); - if (disallowSimultaneous) { + // Legacy properties to not affect max connections per group + connectionGroupDefaultMaxConnections = 0; - // Connections may not be used concurrently - if (disallowDuplicate) - return SingleSeatGuacamoleTunnelService.class; - - // Connections are reserved for a single user when in use - else - return BalancedGuacamoleTunnelService.class; + // Legacy "simultaneous" property dictates only the maximum number of + // connections per connection + if (disallowSimultaneous) + connectionDefaultMaxConnections = 1; + else + connectionDefaultMaxConnections = 0; + // Legacy "duplicate" property dictates whether connections and groups + // may be used concurrently only by different users + if (disallowDuplicate) { + connectionDefaultMaxConnectionsPerUser = 1; + connectionGroupDefaultMaxConnectionsPerUser = 1; } - else { - - // Connections may be used concurrently, but only once per user - if (disallowDuplicate) - return MultiseatGuacamoleTunnelService.class; - - // Connection use is not restricted - else - return UnrestrictedGuacamoleTunnelService.class; - + connectionDefaultMaxConnectionsPerUser = 0; + connectionGroupDefaultMaxConnectionsPerUser = 0; } - + + // Return service configured for specified default limits + return new ConfigurableGuacamoleTunnelService( + connectionDefaultMaxConnections, + connectionDefaultMaxConnectionsPerUser, + connectionGroupDefaultMaxConnections, + connectionGroupDefaultMaxConnectionsPerUser + ); + } /** @@ -123,7 +130,7 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider { new MySQLAuthenticationProviderModule(environment), // Configure JDBC authentication core - new JDBCAuthenticationProviderModule(environment, getSocketServiceClass(environment)) + new JDBCAuthenticationProviderModule(environment, getTunnelService(environment)) ); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/glyptodon/guacamole/auth/postgresql/PostgreSQLAuthenticationProvider.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/glyptodon/guacamole/auth/postgresql/PostgreSQLAuthenticationProvider.java index 1ee8afe2f..5a5d775f4 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/glyptodon/guacamole/auth/postgresql/PostgreSQLAuthenticationProvider.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/glyptodon/guacamole/auth/postgresql/PostgreSQLAuthenticationProvider.java @@ -29,11 +29,8 @@ import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule; -import org.glyptodon.guacamole.auth.jdbc.tunnel.BalancedGuacamoleTunnelService; +import org.glyptodon.guacamole.auth.jdbc.tunnel.ConfigurableGuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.MultiseatGuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.SingleSeatGuacamoleTunnelService; -import org.glyptodon.guacamole.auth.jdbc.tunnel.UnrestrictedGuacamoleTunnelService; import org.glyptodon.guacamole.auth.jdbc.user.UserContextService; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.LocalEnvironment; @@ -54,52 +51,62 @@ public class PostgreSQLAuthenticationProvider implements AuthenticationProvider private final Injector injector; /** - * Returns the appropriate socket service class given the Guacamole - * environment. The class is chosen based on configuration options that - * dictate concurrent usage policy. + * Returns the appropriate tunnel service given the Guacamole environment. + * The service is configured based on configuration options that dictate + * the default concurrent usage policy. * * @param environment * The environment of the Guacamole server. * * @return - * The socket service class that matches the concurrent usage policy - * options set in the Guacamole environment. + * A tunnel service implementation configured according to the + * concurrent usage policy options set in the Guacamole environment. * * @throws GuacamoleException * If an error occurs while reading the configuration options. */ - private Class - getSocketServiceClass(Environment environment) + private GuacamoleTunnelService getTunnelService(Environment environment) throws GuacamoleException { - // Read concurrency-related properties + // Tunnel service default configuration + int connectionDefaultMaxConnections; + int connectionDefaultMaxConnectionsPerUser; + int connectionGroupDefaultMaxConnections; + int connectionGroupDefaultMaxConnectionsPerUser; + + // Read legacy concurrency-related properties boolean disallowSimultaneous = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false); boolean disallowDuplicate = environment.getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_DISALLOW_DUPLICATE_CONNECTIONS, true); - if (disallowSimultaneous) { + // Legacy properties to not affect max connections per group + connectionGroupDefaultMaxConnections = 0; - // Connections may not be used concurrently - if (disallowDuplicate) - return SingleSeatGuacamoleTunnelService.class; - - // Connections are reserved for a single user when in use - else - return BalancedGuacamoleTunnelService.class; + // Legacy "simultaneous" property dictates only the maximum number of + // connections per connection + if (disallowSimultaneous) + connectionDefaultMaxConnections = 1; + else + connectionDefaultMaxConnections = 0; + // Legacy "duplicate" property dictates whether connections and groups + // may be used concurrently only by different users + if (disallowDuplicate) { + connectionDefaultMaxConnectionsPerUser = 1; + connectionGroupDefaultMaxConnectionsPerUser = 1; } - else { - - // Connections may be used concurrently, but only once per user - if (disallowDuplicate) - return MultiseatGuacamoleTunnelService.class; - - // Connection use is not restricted - else - return UnrestrictedGuacamoleTunnelService.class; - + connectionDefaultMaxConnectionsPerUser = 0; + connectionGroupDefaultMaxConnectionsPerUser = 0; } - + + // Return service configured for specified default limits + return new ConfigurableGuacamoleTunnelService( + connectionDefaultMaxConnections, + connectionDefaultMaxConnectionsPerUser, + connectionGroupDefaultMaxConnections, + connectionGroupDefaultMaxConnectionsPerUser + ); + } /** @@ -123,7 +130,7 @@ public class PostgreSQLAuthenticationProvider implements AuthenticationProvider new PostgreSQLAuthenticationProviderModule(environment), // Configure JDBC authentication core - new JDBCAuthenticationProviderModule(environment, getSocketServiceClass(environment)) + new JDBCAuthenticationProviderModule(environment, getTunnelService(environment)) );