From 89f0f4783e0fd5cb2abf833aa045abe9e930cde1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 25 Feb 2015 16:03:59 -0800 Subject: [PATCH] GUAC-1101: Include active connections in history. Insert history records into database when connections close. --- .../net/auth/mysql/MySQLConnection.java | 4 +- .../AbstractGuacamoleSocketService.java | 129 +++++++++++------- .../mysql/service/ActiveConnectionRecord.java | 89 ++++++++++++ .../auth/mysql/service/ConnectionService.java | 27 ++-- .../mysql/service/GuacamoleSocketService.java | 13 +- 5 files changed, 195 insertions(+), 67 deletions(-) create mode 100644 extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ActiveConnectionRecord.java 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 1e938c08b..87f02959f 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 @@ -180,7 +180,7 @@ public class MySQLConnection implements Connection, DirectoryObject getHistory() throws GuacamoleException { - return connectionService.retrieveHistory(currentUser, this.getIdentifier()); + return connectionService.retrieveHistory(currentUser, this); } @Override @@ -190,7 +190,7 @@ public class MySQLConnection implements Connection, DirectoryObject activeConnectionCount = - new ConcurrentHashMap(); + private final Map> activeConnections = + new HashMap>(); /** * Atomically increments the current usage count for the given connection. @@ -75,13 +89,22 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS * @param connection * The connection which is being used. */ - private void incrementUsage(MySQLConnection connection) { + private void addActiveConnection(Connection connection, ConnectionRecord record) { + synchronized (activeConnections) { - // Increment or initialize usage count atomically - AtomicInteger count = activeConnectionCount.putIfAbsent(connection.getIdentifier(), new AtomicInteger(1)); - if (count != null) - count.incrementAndGet(); + String identifier = connection.getIdentifier(); + // Get set of active connection records, creating if necessary + LinkedList connections = activeConnections.get(identifier); + if (connections == null) { + connections = new LinkedList(); + activeConnections.put(identifier, connections); + } + + // Add active connection + connections.addFirst(record); + + } } /** @@ -93,15 +116,23 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS * @param connection * The connection which is no longer being used. */ - private void decrementUsage(MySQLConnection connection) { + private void removeActiveConnection(Connection connection, ConnectionRecord record) { + synchronized (activeConnections) { + + String identifier = connection.getIdentifier(); + + // Get set of active connection records + LinkedList connections = activeConnections.get(identifier); + assert(connections != null); + + // Remove old record + connections.remove(record); + + // If now empty, clean the tracking entry + if (connections.isEmpty()) + activeConnections.remove(identifier); - // 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); } - } /** @@ -135,36 +166,14 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS 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 { + // Create record for active connection + final ActiveConnectionRecord activeConnection = new ActiveConnectionRecord(user); + // Generate configuration from available data GuacamoleConfiguration config = new GuacamoleConfiguration(); @@ -182,7 +191,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS // Atomically gain access to connection acquire(user, connection); - incrementUsage(connection); + addActiveConnection(connection, activeConnection); // Return newly-reserved connection return new ConfiguredGuacamoleSocket( @@ -200,9 +209,22 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS super.close(); // Release connection upon close - decrementUsage(connection); + removeActiveConnection(connection, activeConnection); release(user, connection); + UserModel userModel = user.getUser().getModel(); + ConnectionRecordModel recordModel = new ConnectionRecordModel(); + + // Copy user information and timestamps into new record + recordModel.setUserID(userModel.getUserID()); + recordModel.setUsername(userModel.getUsername()); + recordModel.setConnectionIdentifier(connection.getIdentifier()); + recordModel.setStartDate(activeConnection.getStartDate()); + recordModel.setEndDate(new Date()); + + // Insert connection record + connectionRecordMapper.insert(recordModel); + } }; @@ -213,7 +235,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS catch (GuacamoleException e) { // Atomically release access to connection - decrementUsage(connection); + removeActiveConnection(connection, activeConnection); release(user, connection); throw e; @@ -223,16 +245,19 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS } @Override - public int getActiveConnections(Connection connection) { + public List getActiveConnections(Connection connection) { + synchronized (activeConnections) { - // If no such active connection, zero active users - AtomicInteger count = activeConnectionCount.get(connection.getIdentifier()); - if (count == null) - return 0; + String identifier = connection.getIdentifier(); - // Otherwise, return stored value - return count.intValue(); - + // Get set of active connection records + LinkedList connections = activeConnections.get(identifier); + if (connections != null) + return Collections.unmodifiableList(connections); + + return Collections.EMPTY_LIST; + + } } } diff --git a/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ActiveConnectionRecord.java b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ActiveConnectionRecord.java new file mode 100644 index 000000000..70a9ce7c4 --- /dev/null +++ b/extensions/guacamole-auth-mysql/src/main/java/net/sourceforge/guacamole/net/auth/mysql/service/ActiveConnectionRecord.java @@ -0,0 +1,89 @@ +/* + * 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 java.util.Date; +import net.sourceforge.guacamole.net.auth.mysql.AuthenticatedUser; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; + + +/** + * A connection record implementation that describes an active connection. As + * the associated connection has not yet ended, getEndDate() will always return + * null, and isActive() will always return true. The associated start date will + * be the time of this objects creation. + * + * @author Michael Jumper + */ +public class ActiveConnectionRecord implements ConnectionRecord { + + /** + * The user that connected to the connection associated with this connection + * record. + */ + private final AuthenticatedUser user; + + /** + * The time this connection record was created. + */ + private final Date startDate = new Date(); + + /** + * Creates a new connection record associated with the given user. The + * start date of this connection record will be the time of its creation. + * + * @param user + * The user that connected to the connection associated with this + * connection record. + */ + public ActiveConnectionRecord(AuthenticatedUser user) { + this.user = user; + } + + @Override + public Date getStartDate() { + return startDate; + } + + @Override + public Date getEndDate() { + + // Active connections have not yet ended + return null; + + } + + @Override + public String getUsername() { + return user.getUser().getIdentifier(); + } + + @Override + public boolean isActive() { + + // Active connections are active by definition + return true; + + } + +} 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 a18fc468e..a161b20d6 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 @@ -45,6 +45,7 @@ import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleSecurityException; import org.glyptodon.guacamole.net.GuacamoleSocket; import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; import org.glyptodon.guacamole.net.auth.permission.SystemPermission; @@ -274,29 +275,37 @@ public class ConnectionService extends DirectoryObjectService retrieveHistory(AuthenticatedUser user, - String identifier) throws GuacamoleException { + public List retrieveHistory(AuthenticatedUser user, + MySQLConnection connection) throws GuacamoleException { + String identifier = connection.getIdentifier(); + // Retrieve history only if READ permission is granted if (hasObjectPermission(user, identifier, ObjectPermission.Type.READ)) { // Retrieve history List models = connectionRecordMapper.select(identifier); - // Convert model objects into standard records - List records = new ArrayList(models.size()); + // Get currently-active connections + List records = new ArrayList(socketService.getActiveConnections(connection)); + + // Add past connections from model objects for (ConnectionRecordModel model : models) records.add(new MySQLConnectionRecord(model)); 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 index b3b80bd5d..b534cfc14 100644 --- 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 @@ -22,11 +22,13 @@ package net.sourceforge.guacamole.net.auth.mysql.service; +import java.util.List; 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.net.auth.ConnectionRecord; import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; @@ -68,14 +70,17 @@ public interface GuacamoleSocketService { throws GuacamoleException; /** - * Returns the number of active connections using the given connection. + * Returns a list containing connection records representing all currently- + * active connections using the given connection. These records will have + * usernames and start dates, but no end date. * * @param connection * The connection to check. * * @return - * The number of active connections using the given connection. + * A list containing connection records representing all currently- + * active connections. */ - public int getActiveConnections(Connection connection); - + public List getActiveConnections(Connection connection); + }