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 31e9c6389..a50389f48 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 @@ -46,7 +46,6 @@ import org.glyptodon.guacamole.auth.jdbc.security.SHA256PasswordEncryptionServic import org.glyptodon.guacamole.auth.jdbc.security.SaltService; import org.glyptodon.guacamole.auth.jdbc.security.SecureRandomSaltService; import org.glyptodon.guacamole.auth.jdbc.permission.SystemPermissionService; -import org.glyptodon.guacamole.auth.jdbc.socket.UnrestrictedGuacamoleSocketService; import org.glyptodon.guacamole.auth.jdbc.user.UserService; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.glyptodon.guacamole.auth.jdbc.permission.ConnectionGroupPermissionMapper; @@ -77,15 +76,27 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { */ private final Environment environment; + /** + * The service class to use to provide GuacamoleSockets for each + * connection. + */ + private final Class socketServiceClass; + /** * Creates a new JDBC authentication provider module that configures the - * various injected base classes using the given environment. + * various injected base classes using the given environment, and provides + * connections using the given socket service. * * @param environment * The environment to use to configure injected classes. + * + * @param socketServiceClass + * The socket service to use to provide sockets for connections. */ - public JDBCAuthenticationProviderModule(Environment environment) { + public JDBCAuthenticationProviderModule(Environment environment, + Class socketServiceClass) { this.environment = environment; + this.socketServiceClass = socketServiceClass; } @Override @@ -135,8 +146,8 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { bind(UserPermissionService.class); bind(UserService.class); - // Bind appropriate socket service based on policy - bind(GuacamoleSocketService.class).to(UnrestrictedGuacamoleSocketService.class); + // Bind provided socket service + bind(GuacamoleSocketService.class).to(socketServiceClass); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/MultiseatGuacamoleSocketService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/MultiseatGuacamoleSocketService.java new file mode 100644 index 000000000..a55e31780 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/MultiseatGuacamoleSocketService.java @@ -0,0 +1,138 @@ +/* + * 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.socket; + +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; + + +/** + * GuacamoleSocketService implementation which restricts concurrency only on a + * per-user basis. Each connection 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 MultiseatGuacamoleSocketService + extends AbstractGuacamoleSocketService { + + /** + * A unique pairing of user and connection. + */ + private static class Seat { + + /** + * The user using this seat. + */ + private final String username; + + /** + * The connection associated with this seat. + */ + private final String connectionIdentifier; + + /** + * Creates a new seat which associated the given user with the given + * connection. + * + * @param username + * The username of the user using this seat. + * + * @param connectionIdentifier + * The identifier of the connection associated with this seat. + */ + public Seat(String username, String connectionIdentifier) { + this.username = username; + this.connectionIdentifier = connectionIdentifier; + } + + @Override + public int hashCode() { + + // The various properties will never be null + assert(username != null); + assert(connectionIdentifier != null); + + // Derive hashcode from username and connection identifier + int hash = 5; + hash = 37 * hash + username.hashCode(); + hash = 37 * hash + connectionIdentifier.hashCode(); + return hash; + + } + + @Override + public boolean equals(Object object) { + + // We are only comparing against other seats here + assert(object instanceof Seat); + Seat seat = (Seat) object; + + // The various properties will never be null + assert(seat.username != null); + assert(seat.connectionIdentifier != null); + + return username.equals(seat.username) + && connectionIdentifier.equals(seat.connectionIdentifier); + + } + + } + + /** + * The set of all active user/connection pairs. + */ + private final Set activeSeats = + Collections.newSetFromMap(new ConcurrentHashMap()); + + @Override + protected ModeledConnection acquire(AuthenticatedUser user, + List connections) throws GuacamoleException { + + String username = user.getUser().getIdentifier(); + + // Return the first unreserved connection + for (ModeledConnection connection : connections) { + 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())); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/ReservedGuacamoleSocketService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/ReservedGuacamoleSocketService.java new file mode 100644 index 000000000..050e40c17 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/ReservedGuacamoleSocketService.java @@ -0,0 +1,59 @@ +/* + * 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.socket; + +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; + + +/** + * GuacamoleSocketService implementation which allows only one user per + * connection at any time, but does not disallow concurrent use. Once + * connected, a user has effectively reserved that connection, and may + * continue to concurrently use that connection any number of times. The + * connection will remain reserved until all associated connections are closed. + * Other users will be denied access to that connection while it is reserved. + * + * @author Michael Jumper + */ +@Singleton +public class ReservedGuacamoleSocketService + extends AbstractGuacamoleSocketService { + + @Override + protected ModeledConnection acquire(AuthenticatedUser user, + List connections) throws GuacamoleException { + // STUB + throw new UnsupportedOperationException("STUB"); + } + + @Override + protected void release(AuthenticatedUser user, ModeledConnection connection) { + // STUB + throw new UnsupportedOperationException("STUB"); + } + +} diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/SingleSeatGuacamoleSocketService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/SingleSeatGuacamoleSocketService.java new file mode 100644 index 000000000..e3ff6a9a4 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/SingleSeatGuacamoleSocketService.java @@ -0,0 +1,72 @@ +/* + * 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.socket; + +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; + + +/** + * GuacamoleSocketService implementation which allows exactly one use + * of any connection at any time. Concurrent usage of any kind is not allowed. + * + * @author Michael Jumper + */ +@Singleton +public class SingleSeatGuacamoleSocketService + extends AbstractGuacamoleSocketService { + + /** + * 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()); + } + +} 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 8547fcf3d..c33787a38 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 @@ -29,9 +29,15 @@ 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.socket.GuacamoleSocketService; +import org.glyptodon.guacamole.auth.jdbc.socket.MultiseatGuacamoleSocketService; +import org.glyptodon.guacamole.auth.jdbc.socket.ReservedGuacamoleSocketService; +import org.glyptodon.guacamole.auth.jdbc.socket.SingleSeatGuacamoleSocketService; +import org.glyptodon.guacamole.auth.jdbc.socket.UnrestrictedGuacamoleSocketService; import org.glyptodon.guacamole.auth.jdbc.user.UserContextService; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.environment.LocalEnvironment; +import org.glyptodon.guacamole.properties.GuacamoleProperties; /** * Provides a MySQL based implementation of the AuthenticationProvider @@ -48,6 +54,55 @@ 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. + * + * @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. + * + * @throws GuacamoleException + * If an error occurs while reading the configuration options. + */ + private Class + getSocketServiceClass(Environment environment) + throws GuacamoleException { + + // Read 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) { + + // Connections may not be used concurrently + if (disallowDuplicate) + return SingleSeatGuacamoleSocketService.class; + + // Connections are reserved for a single user when in use + else + return ReservedGuacamoleSocketService.class; + + } + + else { + + // Connections may be used concurrently, but only once per user + if (disallowDuplicate) + return MultiseatGuacamoleSocketService.class; + + // Connection use is not restricted + else + return UnrestrictedGuacamoleSocketService.class; + + } + + } + /** * Creates a new MySQLAuthenticationProvider that reads and writes * authentication data to a MySQL database defined by properties in @@ -69,7 +124,7 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider { new MySQLAuthenticationProviderModule(environment), // Configure JDBC authentication core - new JDBCAuthenticationProviderModule(environment) + new JDBCAuthenticationProviderModule(environment, getSocketServiceClass(environment)) );