diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/ActiveConnectionService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/ActiveConnectionService.java index 1d3344db9..c21e9c36c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/ActiveConnectionService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/ActiveConnectionService.java @@ -91,15 +91,16 @@ public class ActiveConnectionService Collection activeConnections = new ArrayList(identifiers.size()); for (ActiveConnectionRecord record : records) { - // Sensitive information should be included if the connection was - // started by the current user OR the user is an admin - boolean includeSensitiveInformation = + // The current user should have access to sensitive information and + // be able to connect to (join) the active connection if they are + // the user that started the connection OR the user is an admin + boolean hasPrivilegedAccess = isAdmin || username.equals(record.getUsername()); // Add connection if within requested identifiers if (identifierSet.contains(record.getUUID().toString())) { TrackedActiveConnection activeConnection = trackedActiveConnectionProvider.get(); - activeConnection.init(user, record, includeSensitiveInformation); + activeConnection.init(user, record, hasPrivilegedAccess, hasPrivilegedAccess); activeConnections.add(activeConnection); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java index 54245503d..cdbcc0734 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/activeconnection/TrackedActiveConnection.java @@ -21,15 +21,20 @@ package org.apache.guacamole.auth.jdbc.activeconnection; import com.google.inject.Inject; import java.util.Date; +import java.util.Map; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.auth.jdbc.base.RestrictedObject; import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; import org.apache.guacamole.auth.jdbc.sharing.ConnectionSharingService; +import org.apache.guacamole.auth.jdbc.sharing.connection.SharedConnectionDefinition; import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord; +import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.ActiveConnection; import org.apache.guacamole.net.auth.credentials.UserCredentials; +import org.apache.guacamole.protocol.GuacamoleClientInformation; /** * An implementation of the ActiveConnection object which has an associated @@ -43,6 +48,12 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC @Inject private ConnectionSharingService sharingService; + /** + * Service for creating and tracking tunnels. + */ + @Inject + private GuacamoleTunnelService tunnelService; + /** * The identifier of this active connection. */ @@ -84,6 +95,11 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC */ private GuacamoleTunnel tunnel; + /** + * Whether connections to this TrackedActiveConnection are allowed. + */ + private boolean connectable; + /** * Initializes this TrackedActiveConnection, copying the data associated * with the given active connection record. At a minimum, the identifier @@ -102,13 +118,19 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC * Whether sensitive data should be copied from the connection record * as well. This includes the remote host, associated tunnel, and * username. + * + * @param connectable + * Whether the user that retrieved this object should be allowed to + * join the active connection. */ public void init(ModeledAuthenticatedUser currentUser, ActiveConnectionRecord activeConnectionRecord, - boolean includeSensitiveInformation) { + boolean includeSensitiveInformation, + boolean connectable) { super.init(currentUser); this.connectionRecord = activeConnectionRecord; + this.connectable = connectable; // Copy all non-sensitive data from given record this.connection = activeConnectionRecord.getConnection(); @@ -169,11 +191,32 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC this.sharingProfileIdentifier = sharingProfileIdentifier; } + /** + * Shares this active connection with the user that retrieved it, returning + * a SharedConnectionDefinition that can be used to establish a tunnel to + * the shared connection. If provided, access within the shared connection + * will be restricted by the sharing profile with the given identifier. + * + * @param identifier + * The identifier of the sharing profile that defines the restrictions + * applying to the shared connection, or null if no such restrictions + * apply. + * + * @return + * A new SharedConnectionDefinition which can be used to establish a + * tunnel to the shared connection. + * + * @throws GuacamoleException + * If permission to share this active connection is denied. + */ + private SharedConnectionDefinition share(String identifier) throws GuacamoleException { + return sharingService.shareConnection(getCurrentUser(), connectionRecord, identifier); + } + @Override public UserCredentials getSharingCredentials(String identifier) throws GuacamoleException { - return sharingService.generateTemporaryCredentials(getCurrentUser(), - connectionRecord, identifier); + return sharingService.getSharingCredentials(share(identifier)); } @Override @@ -216,4 +259,26 @@ public class TrackedActiveConnection extends RestrictedObject implements ActiveC this.tunnel = tunnel; } + @Override + public boolean isConnectable() { + return connectable; + } + + @Override + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + + // Establish connection only if connecting is allowed + if (isConnectable()) + return tunnelService.getGuacamoleTunnel(getCurrentUser(), share(null), info, tokens); + + throw new GuacamoleSecurityException("Permission denied."); + + } + + @Override + public int getActiveConnections() { + return 0; + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java index efdecf045..fe038b6cb 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/ConnectionSharingService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.jdbc.sharing; import com.google.inject.Inject; import java.util.Collections; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.GuacamoleException; @@ -30,11 +31,14 @@ import org.apache.guacamole.auth.jdbc.sharing.user.SharedAuthenticatedUser; import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile; import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileService; import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord; +import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService; +import org.apache.guacamole.auth.jdbc.user.RemoteAuthenticatedUser; import org.apache.guacamole.form.Field; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.UserCredentials; +import org.apache.guacamole.protocol.GuacamoleClientInformation; /** * Service which provides convenience methods for sharing active connections. @@ -75,10 +79,16 @@ public class ConnectionSharingService { )); /** - * Generates a set of temporary credentials which can be used to connect to - * the given connection using the given sharing profile. If the user does - * not have permission to share the connection via the given sharing - * profile, permission will be denied. + * Creates a new SharedConnectionDefinition which can be used to connect to + * the given connection, optionally restricting access to the shared + * connection using the given sharing profile. If the user does not have + * permission to share the connection via the given sharing profile, + * permission will be denied. + * + * @see GuacamoleTunnelService#getGuacamoleTunnel(RemoteAuthenticatedUser, + * SharedConnectionDefinition, GuacamoleClientInformation, Map) + * + * @see #getSharingCredentials(SharedConnectionDefinition) * * @param user * The user sharing the connection. @@ -88,42 +98,67 @@ public class ConnectionSharingService { * * @param sharingProfileIdentifier * The identifier of the sharing profile dictating the semantics or - * restrictions applying to the shared session. + * restrictions applying to the shared session, or null if no such + * restrictions should apply. * * @return - * A newly-generated set of temporary credentials which can be used to - * connect to the given connection. + * A new SharedConnectionDefinition which can be used to connect to the + * given connection. * * @throws GuacamoleException * If permission to share the given connection is denied. */ - public UserCredentials generateTemporaryCredentials(ModeledAuthenticatedUser user, + public SharedConnectionDefinition shareConnection(ModeledAuthenticatedUser user, ActiveConnectionRecord activeConnection, String sharingProfileIdentifier) throws GuacamoleException { - // Pull sharing profile (verifying access) - ModeledSharingProfile sharingProfile = - sharingProfileService.retrieveObject(user, - sharingProfileIdentifier); + // If a sharing profile is provided, verify that permission to use that + // profile to share the given connection is actually granted + ModeledSharingProfile sharingProfile = null; + if (sharingProfileIdentifier != null) { - // Verify that this profile is indeed a sharing profile for the - // requested connection - String connectionIdentifier = activeConnection.getConnectionIdentifier(); - if (sharingProfile == null || !sharingProfile.getPrimaryConnectionIdentifier().equals(connectionIdentifier)) - throw new GuacamoleSecurityException("Permission denied."); + // Pull sharing profile (verifying access) + sharingProfile = sharingProfileService.retrieveObject(user, sharingProfileIdentifier); + + // Verify that this profile is indeed a sharing profile for the + // requested connection + String connectionIdentifier = activeConnection.getConnectionIdentifier(); + if (sharingProfile == null || !sharingProfile.getPrimaryConnectionIdentifier().equals(connectionIdentifier)) + throw new GuacamoleSecurityException("Permission denied."); + + } // Generate a share key for the requested connection String key = keyGenerator.getShareKey(); - connectionMap.add(new SharedConnectionDefinition(activeConnection, - sharingProfile, key)); + SharedConnectionDefinition definition = new SharedConnectionDefinition(activeConnection, sharingProfile, key); + connectionMap.add(definition); // Ensure the share key is properly invalidated when the original // connection is closed activeConnection.registerShareKey(key); + return definition; + + } + + /** + * Generates a set of temporary credentials which can be used to connect to + * the given connection shared by the SharedConnectionDefinition. + * + * @param definition + * The SharedConnectionDefinition which defines the connection being + * shared and any applicable restrictions. + * + * @return + * A newly-generated set of temporary credentials which can be used to + * connect to the connection shared by the given + * SharedConnectionDefinition. + */ + public UserCredentials getSharingCredentials(SharedConnectionDefinition definition) { + // Return credentials defining a single expected parameter return new UserCredentials(SHARE_KEY, - Collections.singletonMap(SHARE_KEY_NAME, key)); + Collections.singletonMap(SHARE_KEY_NAME, definition.getShareKey())); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnectionDefinition.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnectionDefinition.java index cb48013a9..4e7c3d5e0 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnectionDefinition.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnectionDefinition.java @@ -29,9 +29,11 @@ import org.slf4j.LoggerFactory; /** * Defines the semantics/restrictions of a shared connection by associating an - * active connection with a sharing profile. The sharing profile defines the - * access provided to users of the shared active connection through its - * connection parameters. + * active connection with an optional sharing profile. The sharing profile, if + * present, defines the access provided to users of the shared active + * connection through its connection parameters. If no sharing profile is + * present, the shared connection has the same level of access as the original + * connection. */ public class SharedConnectionDefinition { @@ -88,7 +90,8 @@ public class SharedConnectionDefinition { * * @param sharingProfile * A sharing profile whose associated parameters dictate the level of - * access provided to the shared connection. + * access provided to the shared connection, or null if the connection + * should be given full access. * * @param shareKey * The unique key with which a user may access the shared connection. diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 20ac29995..abecf32f8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -202,8 +202,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS /** * Returns a guacamole configuration containing the protocol and parameters - * from the given connection. If tokens are used in the connection - * parameter values, credentials from the given user will be substituted + * from the given connection. If the ID of an active connection is + * provided, that connection will be joined instead of starting a new + * primary connection. If tokens are used in the connection parameter + * values, credentials from the given user will be substituted * appropriately. * * @param user @@ -213,19 +215,29 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * The connection whose protocol and parameters should be added to the * returned configuration. * + * @param connectionID + * The ID of the active connection to be joined, as returned by guacd, + * or null if a new primary connection should be established. + * * @return * A GuacamoleConfiguration containing the protocol and parameters from * the given connection. */ private GuacamoleConfiguration getGuacamoleConfiguration(RemoteAuthenticatedUser user, - ModeledConnection connection) { + ModeledConnection connection, String connectionID) { // Generate configuration from available data GuacamoleConfiguration config = new GuacamoleConfiguration(); - // Set protocol from connection - ConnectionModel model = connection.getModel(); - config.setProtocol(model.getProtocol()); + // Join existing active connection, if any + if (connectionID != null) + config.setConnectionID(connectionID); + + // Set protocol from connection if not joining an active connection + else { + ConnectionModel model = connection.getModel(); + config.setProtocol(model.getProtocol()); + } // Set parameters from associated data Collection parameters = connectionParameterMapper.select(connection.getIdentifier()); @@ -470,16 +482,17 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS // Retrieve connection information associated with given connection record ModeledConnection connection = activeConnection.getConnection(); - // Pull configuration directly from the connection if we are not - // joining an active connection + // Pull configuration directly from the connection, additionally + // joining the existing active connection (without sharing profile + // restrictions) if such a connection exists if (activeConnection.isPrimaryConnection()) { activeConnections.put(connection.getIdentifier(), activeConnection); activeConnectionGroups.put(connection.getParentIdentifier(), activeConnection); - config = getGuacamoleConfiguration(activeConnection.getUser(), connection); + config = getGuacamoleConfiguration(activeConnection.getUser(), connection, activeConnection.getConnectionID()); } - // If we ARE joining an active connection, generate a configuration - // which does so + // If we ARE joining an active connection under the restrictions of + // a sharing profile, generate a configuration which does so else { // Verify that the connection ID is known diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java index 3a4b1484d..a150212c8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/ActiveConnectionRecord.java @@ -32,7 +32,6 @@ import org.apache.guacamole.net.AbstractGuacamoleTunnel; import org.apache.guacamole.net.GuacamoleSocket; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.ConnectionRecord; -import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket; /** @@ -202,8 +201,8 @@ public class ActiveConnectionRecord implements ConnectionRecord { * * @param sharingProfile * The sharing profile that was used to share access to the given - * connection. As a record created in this way always refers to a - * shared connection, this value may NOT be null. + * connection, or null if no sharing profile should be used (access to + * the connection is unrestricted). */ public void init(RemoteAuthenticatedUser user, ActiveConnectionRecord activeConnection,