diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java index c2e75c7fa..f3ec72410 100644 --- a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/MySQLAuthenticationProvider.java @@ -39,11 +39,13 @@ import org.glyptodon.guacamole.net.auth.UserContext; import net.sourceforge.guacamole.net.auth.mysql.dao.UserMapper; import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties; import net.sourceforge.guacamole.net.auth.mysql.service.ConnectionService; +import net.sourceforge.guacamole.net.auth.mysql.service.GuacamoleSocketService; import net.sourceforge.guacamole.net.auth.mysql.service.PasswordEncryptionService; import net.sourceforge.guacamole.net.auth.mysql.service.SHA256PasswordEncryptionService; import net.sourceforge.guacamole.net.auth.mysql.service.SaltService; import net.sourceforge.guacamole.net.auth.mysql.service.SecureRandomSaltService; import net.sourceforge.guacamole.net.auth.mysql.service.SystemPermissionService; +import net.sourceforge.guacamole.net.auth.mysql.service.UnrestrictedGuacamoleSocketService; import net.sourceforge.guacamole.net.auth.mysql.service.UserService; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.glyptodon.guacamole.environment.Environment; @@ -164,6 +166,9 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider { bind(SystemPermissionService.class); bind(UserService.class); + // Bind appropriate socket service based on policy + bind(GuacamoleSocketService.class).to(UnrestrictedGuacamoleSocketService.class); + } } // end of mybatis module 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 ffb5d0c90..fed0d4660 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 @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionModel; import net.sourceforge.guacamole.net.auth.mysql.service.ConnectionService; +import net.sourceforge.guacamole.net.auth.mysql.service.GuacamoleSocketService; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.GuacamoleSocket; import org.glyptodon.guacamole.net.auth.Connection; @@ -59,6 +60,12 @@ public class MySQLConnection implements Connection, DirectoryObject activeConnectionCount = + new ConcurrentHashMap(); + + /** + * Atomically increments the current usage count for the given connection. + * + * @param connection + * The connection which is being used. + */ + private void incrementUsage(MySQLConnection connection) { + + // Increment or initialize usage count atomically + AtomicInteger count = activeConnectionCount.putIfAbsent(connection.getIdentifier(), new AtomicInteger(1)); + if (count != null) + count.incrementAndGet(); + + } + + /** + * Atomically decrements the current usage count for the given connection. + * If a combination of incrementUsage() and decrementUsage() calls result + * in the usage counter being reduced to zero, it is guaranteed that one + * of those decrementUsage() calls will remove the value from the map. + * + * @param connection + * The connection which is no longer being used. + */ + private void decrementUsage(MySQLConnection connection) { + + // Decrement usage count, remove entry if it becomes zero + AtomicInteger count = activeConnectionCount.get(connection.getIdentifier()); + if (count != null) { + count.decrementAndGet(); + activeConnectionCount.remove(connection.getIdentifier(), 0); + } + + } + + /** + * Acquires possibly-exclusive access to the given connection on behalf of + * the given user. If access is denied for any reason, an exception is + * thrown. + * + * @param user + * The user acquiring access. + * + * @param connection + * The connection being accessed. + * + * @throws GuacamoleException + * If access is denied to the given user for any reason. + */ + protected abstract void acquire(AuthenticatedUser user, + MySQLConnection connection) throws GuacamoleException; + + /** + * Releases possibly-exclusive access to the given connection on behalf of + * the given user. If the given user did not already have access, the + * behavior of this function is undefined. + * + * @param user + * The user releasing access. + * + * @param connection + * The connection being released. + */ + protected abstract void release(AuthenticatedUser user, + MySQLConnection connection); + + /** + * Creates a socket for the given user which connects to the given + * connection. The given client information will be passed to guacd when + * the connection is established. This function will apply any concurrent + * usage rules in effect, but will NOT test object- or system-level + * permissions. + * + * @param user + * The user for whom the connection is being established. + * + * @param connection + * The connection the user is connecting to. + * + * @param info + * Information describing the Guacamole client connecting to the given + * connection. + * + * @return + * A new GuacamoleSocket which is configured and connected to the given + * connection. + * + * @throws GuacamoleException + * If the connection cannot be established due to concurrent usage + * rules. + */ + @Override + public GuacamoleSocket getGuacamoleSocket(final AuthenticatedUser user, + final MySQLConnection connection, GuacamoleClientInformation info) + throws GuacamoleException { + + // Generate configuration from available data + GuacamoleConfiguration config = new GuacamoleConfiguration(); + + // Set protocol from connection + ConnectionModel model = connection.getModel(); + config.setProtocol(model.getProtocol()); + + // Set parameters from associated data + Collection parameters = parameterMapper.select(connection.getIdentifier()); + for (ParameterModel parameter : parameters) + config.setParameter(parameter.getName(), parameter.getValue()); + + // Return new socket + try { + + // Atomically gain access to connection + acquire(user, connection); + incrementUsage(connection); + + // Return newly-reserved connection + return new ConfiguredGuacamoleSocket( + new InetGuacamoleSocket( + environment.getRequiredProperty(Environment.GUACD_HOSTNAME), + environment.getRequiredProperty(Environment.GUACD_PORT) + ), + config + ) { + + @Override + public void close() throws GuacamoleException { + + // Attempt to close connection + super.close(); + + // Release connection upon close + decrementUsage(connection); + release(user, connection); + + } + + }; + + } + + // Release connection in case of error + catch (GuacamoleException e) { + + // Atomically release access to connection + decrementUsage(connection); + release(user, connection); + + throw e; + + } + + } + + @Override + public int getActiveConnections(Connection connection) { + + // If no such active connection, zero active users + AtomicInteger count = activeConnectionCount.get(connection.getIdentifier()); + if (count == null) + return 0; + + // Otherwise, return stored value + return count.intValue(); + + } + +} 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 4bab4d194..d038ff94c 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 @@ -24,8 +24,6 @@ package net.sourceforge.guacamole.net.auth.mysql.service; import com.google.inject.Inject; import com.google.inject.Provider; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -39,17 +37,13 @@ import net.sourceforge.guacamole.net.auth.mysql.model.ParameterModel; import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleSecurityException; -import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.net.GuacamoleSocket; -import org.glyptodon.guacamole.net.InetGuacamoleSocket; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; import org.glyptodon.guacamole.net.auth.permission.SystemPermission; import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet; -import org.glyptodon.guacamole.protocol.ConfiguredGuacamoleSocket; import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; -import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; /** * Service which provides convenience methods for creating, retrieving, and @@ -59,12 +53,6 @@ import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; */ public class ConnectionService extends DirectoryObjectService { - /** - * The environment of the Guacamole server. - */ - @Inject - private Environment environment; - /** * Mapper for accessing connections. */ @@ -83,6 +71,12 @@ public class ConnectionService extends DirectoryObjectService mySQLConnectionProvider; + /** + * Service for creating and tracking sockets. + */ + @Inject + private GuacamoleSocketService socketService; + @Override protected DirectoryObjectMapper getObjectMapper() { return connectionMapper; @@ -252,33 +246,9 @@ public class ConnectionService extends DirectoryObjectService parameters = parameterMapper.select(identifier); - for (ParameterModel parameter : parameters) - config.setParameter(parameter.getName(), parameter.getValue()); - - // Return new socket - return new ConfiguredGuacamoleSocket( - new InetGuacamoleSocket( - environment.getRequiredProperty(Environment.GUACD_HOSTNAME), - environment.getRequiredProperty(Environment.GUACD_PORT) - ), - config - ); - - } + if (hasObjectPermission(user, connection.getIdentifier(), ObjectPermission.Type.READ)) + return socketService.getGuacamoleSocket(user, connection, info); // The user does not have permission to connect throw new GuacamoleSecurityException("Permission denied."); diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/GuacamoleSocketService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/GuacamoleSocketService.java new file mode 100644 index 000000000..b3b80bd5d --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/GuacamoleSocketService.java @@ -0,0 +1,81 @@ +/* + * 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 net.sourceforge.guacamole.net.auth.mysql.service; + +import net.sourceforge.guacamole.net.auth.mysql.AuthenticatedUser; +import net.sourceforge.guacamole.net.auth.mysql.MySQLConnection; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.GuacamoleSocket; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; + + +/** + * Service which creates pre-configured GuacamoleSocket instances for + * connections and balancing groups, applying concurrent usage rules. + * + * @author Michael Jumper + */ +public interface GuacamoleSocketService { + + /** + * Creates a socket for the given user which connects to the given + * connection. The given client information will be passed to guacd when + * the connection is established. This function will apply any concurrent + * usage rules in effect, but will NOT test object- or system-level + * permissions. + * + * @param user + * The user for whom the connection is being established. + * + * @param connection + * The connection the user is connecting to. + * + * @param info + * Information describing the Guacamole client connecting to the given + * connection. + * + * @return + * A new GuacamoleSocket which is configured and connected to the given + * connection. + * + * @throws GuacamoleException + * If the connection cannot be established due to concurrent usage + * rules. + */ + GuacamoleSocket getGuacamoleSocket(AuthenticatedUser user, + MySQLConnection connection, GuacamoleClientInformation info) + throws GuacamoleException; + + /** + * Returns the number of active connections using the given connection. + * + * @param connection + * The connection to check. + * + * @return + * The number of active connections using the given connection. + */ + public int getActiveConnections(Connection connection); + +} diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/UnrestrictedGuacamoleSocketService.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/UnrestrictedGuacamoleSocketService.java new file mode 100644 index 000000000..c50d8f2d2 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/UnrestrictedGuacamoleSocketService.java @@ -0,0 +1,52 @@ +/* + * 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 net.sourceforge.guacamole.net.auth.mysql.service; + +import com.google.inject.Singleton; +import net.sourceforge.guacamole.net.auth.mysql.AuthenticatedUser; +import net.sourceforge.guacamole.net.auth.mysql.MySQLConnection; +import org.glyptodon.guacamole.GuacamoleException; + + +/** + * GuacamoleSocketService implementation which imposes no restrictions + * whatsoever on the number of concurrent or duplicate connections. + * + * @author Michael Jumper + */ +@Singleton +public class UnrestrictedGuacamoleSocketService + extends AbstractGuacamoleSocketService { + + @Override + protected void acquire(AuthenticatedUser user, MySQLConnection connection) + throws GuacamoleException { + // Do nothing + } + + @Override + protected void release(AuthenticatedUser user, MySQLConnection connection) { + // Do nothing + } + +}