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, diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActiveConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActiveConnection.java index ad1d6d37d..9bcce3edd 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActiveConnection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/ActiveConnection.java @@ -20,13 +20,18 @@ package org.apache.guacamole.net.auth; import java.util.Date; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.net.GuacamoleTunnel; +import org.apache.guacamole.protocol.GuacamoleClientInformation; /** * A pairing of username and GuacamoleTunnel representing an active usage of a * particular connection. */ -public interface ActiveConnection extends Identifiable, Shareable { +public interface ActiveConnection extends Identifiable, Connectable, + Shareable { /** * Returns the identifier of the connection being actively used. Unlike the @@ -136,5 +141,31 @@ public interface ActiveConnection extends Identifiable, Shareable tokens) throws GuacamoleException { + throw new GuacamoleSecurityException("Permission denied."); + } + + @Override + default int getActiveConnections() { + return 0; + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/APIActiveConnection.java b/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/APIActiveConnection.java index 0041a03cb..1634378b4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/APIActiveConnection.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/activeconnection/APIActiveConnection.java @@ -54,6 +54,11 @@ public class APIActiveConnection { */ private final String username; + /** + * Whether this active connection may be connected to. + */ + private final boolean connectable; + /** * Creates a new APIActiveConnection, copying the information from the given * active connection. @@ -67,6 +72,7 @@ public class APIActiveConnection { this.startDate = connection.getStartDate(); this.remoteHost = connection.getRemoteHost(); this.username = connection.getUsername(); + this.connectable = connection.isConnectable(); } /** @@ -121,5 +127,16 @@ public class APIActiveConnection { public String getIdentifier() { return identifier; } - + + /*** + * Returns whether this active connection may be connected to, just as a + * normal connection. + * + * @return + * true if this active connection may be connected to, false otherwise. + */ + public boolean isConnectable() { + return connectable; + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java index 74e3b4dcf..fe58e18d4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java @@ -21,8 +21,6 @@ package org.apache.guacamole.tunnel; import java.util.List; import org.apache.guacamole.GuacamoleClientException; -import org.apache.guacamole.GuacamoleClientException; -import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException; /** @@ -101,40 +99,6 @@ public abstract class TunnelRequest { */ public static final String TIMEZONE_PARAMETER = "GUAC_TIMEZONE"; - /** - * All supported object types that can be used as the destination of a - * tunnel. - */ - public static enum Type { - - /** - * A Guacamole connection. - */ - CONNECTION("c"), - - /** - * A Guacamole connection group. - */ - CONNECTION_GROUP("g"); - - /** - * The parameter value which denotes a destination object of this type. - */ - final String PARAMETER_VALUE; - - /** - * Defines a Type having the given corresponding parameter value. - * - * @param value - * The parameter value which denotes a destination object of this - * type. - */ - Type(String value) { - PARAMETER_VALUE = value; - } - - }; - /** * Returns the value of the parameter having the given name. * @@ -257,18 +221,11 @@ public abstract class TunnelRequest { * If the type was not present in the request, or if the type requested * is in the wrong format. */ - public Type getType() throws GuacamoleException { + public TunnelRequestType getType() throws GuacamoleException { - String type = getRequiredParameter(TYPE_PARAMETER); - - // For each possible object type - for (Type possibleType : Type.values()) { - - // Match against defined parameter value - if (type.equals(possibleType.PARAMETER_VALUE)) - return possibleType; - - } + TunnelRequestType type = TunnelRequestType.parseType(getRequiredParameter(TYPE_PARAMETER)); + if (type != null) + return type; throw new GuacamoleClientException("Illegal identifier - unknown type."); diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java index 598a4e57b..fa56b1932 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java @@ -24,15 +24,13 @@ import com.google.inject.Singleton; import java.util.List; import java.util.Map; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.AuthenticatedUser; -import org.apache.guacamole.net.auth.Connection; -import org.apache.guacamole.net.auth.ConnectionGroup; +import org.apache.guacamole.net.auth.Connectable; import org.apache.guacamole.net.auth.Credentials; -import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.event.TunnelCloseEvent; import org.apache.guacamole.net.event.TunnelConnectEvent; @@ -204,58 +202,20 @@ public class TunnelRequestService { * If an error occurs while creating the tunnel. */ protected GuacamoleTunnel createConnectedTunnel(UserContext context, - final TunnelRequest.Type type, String id, + final TunnelRequestType type, String id, GuacamoleClientInformation info, Map tokens) throws GuacamoleException { - // Create connected tunnel from identifier - GuacamoleTunnel tunnel = null; - switch (type) { - - // Connection identifiers - case CONNECTION: { - - // Get connection directory - Directory directory = context.getConnectionDirectory(); - - // Get authorized connection - Connection connection = directory.get(id); - if (connection == null) { - logger.info("Connection \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier()); - throw new GuacamoleSecurityException("Requested connection is not authorized."); - } - - // Connect tunnel - tunnel = connection.connect(info, tokens); - logger.info("User \"{}\" connected to connection \"{}\".", context.self().getIdentifier(), id); - break; - } - - // Connection group identifiers - case CONNECTION_GROUP: { - - // Get connection group directory - Directory directory = context.getConnectionGroupDirectory(); - - // Get authorized connection group - ConnectionGroup group = directory.get(id); - if (group == null) { - logger.info("Connection group \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier()); - throw new GuacamoleSecurityException("Requested connection group is not authorized."); - } - - // Connect tunnel - tunnel = group.connect(info, tokens); - logger.info("User \"{}\" connected to group \"{}\".", context.self().getIdentifier(), id); - break; - } - - // Type is guaranteed to be one of the above - default: - assert(false); - - } + // Retrieve requested destination object + Connectable connectable = type.getConnectable(context, id); + if (connectable == null) + throw new GuacamoleResourceNotFoundException("Requested tunnel " + + "destination does not exist."); + // Connect tunnel to destination + GuacamoleTunnel tunnel = connectable.connect(info, tokens); + logger.info("User \"{}\" connected to {} \"{}\".", + context.self().getIdentifier(), type.NAME, id); return tunnel; } @@ -297,7 +257,7 @@ public class TunnelRequestService { */ protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleTunnel tunnel, final String authToken, final GuacamoleSession session, - final UserContext context, final TunnelRequest.Type type, + final UserContext context, final TunnelRequestType type, final String id) throws GuacamoleException { // Monitor tunnel closure and data @@ -320,26 +280,9 @@ public class TunnelRequestService { long connectionEndTime = System.currentTimeMillis(); long duration = connectionEndTime - connectionStartTime; - // Log closure - switch (type) { - - // Connection identifiers - case CONNECTION: - logger.info("User \"{}\" disconnected from connection \"{}\". Duration: {} milliseconds", - session.getAuthenticatedUser().getIdentifier(), id, duration); - break; - - // Connection group identifiers - case CONNECTION_GROUP: - logger.info("User \"{}\" disconnected from connection group \"{}\". Duration: {} milliseconds", - session.getAuthenticatedUser().getIdentifier(), id, duration); - break; - - // Type is guaranteed to be one of the above - default: - assert(false); - - } + logger.info("User \"{}\" disconnected from {} \"{}\". Duration: {} milliseconds", + session.getAuthenticatedUser().getIdentifier(), + type.NAME, id, duration); try { @@ -390,7 +333,7 @@ public class TunnelRequestService { // Parse request parameters String authToken = request.getAuthenticationToken(); String id = request.getIdentifier(); - TunnelRequest.Type type = request.getType(); + TunnelRequestType type = request.getType(); String authProviderIdentifier = request.getAuthenticationProviderIdentifier(); GuacamoleClientInformation info = getClientInformation(request); diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestType.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestType.java new file mode 100644 index 000000000..2c987081b --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestType.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.tunnel; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.ActiveConnection; +import org.apache.guacamole.net.auth.Connectable; +import org.apache.guacamole.net.auth.Connection; +import org.apache.guacamole.net.auth.ConnectionGroup; +import org.apache.guacamole.net.auth.UserContext; + +/** + * All supported object types that can be used as the destination of a tunnel. + * + * @see TunnelRequest#TYPE_PARAMETER + * @see TunnelRequest#getType() + */ +public enum TunnelRequestType { + + /** + * A Guacamole connection. + */ + CONNECTION("c", "connection") { + + @Override + public Connection getConnectable(UserContext userContext, + String identifier) throws GuacamoleException { + return userContext.getConnectionDirectory().get(identifier); + } + + }, + + /** + * A Guacamole connection group. + */ + CONNECTION_GROUP("g", "connection group") { + + @Override + public ConnectionGroup getConnectable(UserContext userContext, + String identifier) throws GuacamoleException { + return userContext.getConnectionGroupDirectory().get(identifier); + } + + }, + + /** + * An active Guacamole connection. + */ + ACTIVE_CONNECTION("a", "active connection") { + + @Override + public ActiveConnection getConnectable(UserContext userContext, + String identifier) throws GuacamoleException { + return userContext.getActiveConnectionDirectory().get(identifier); + } + + }; + + /** + * The parameter value which denotes a destination object of this type + * within a tunnel request. + * + * @see TunnelRequest#TYPE_PARAMETER + * @see TunnelRequest#getType() + */ + public final String PARAMETER_VALUE; + + /** + * A human-readable, descriptive name of the type of destination object. + */ + public final String NAME; + + /** + * Defines a tunnel request type having the given corresponding parameter + * value and human-readable name. + * + * @param value + * The parameter value which denotes a destination object of this + * type. + * + * @param name + * A human-readable, descriptive name of the type of destination + * object. + */ + private TunnelRequestType(String value, String name) { + PARAMETER_VALUE = value; + NAME = name; + } + + /** + * Retrieves the object having the given identifier from the given + * UserContext, where the type of object retrieved is the type of object + * represented by this tunnel request type. + * + * @param userContext + * The UserContext to retrieve the object from. + * + * @param identifier + * The identifier of the object to retrieve. + * + * @return + * The object having the given identifier, or null if no such object + * exists. + * + * @throws GuacamoleException + * If an error occurs retrieving the requested object, or if permission + * to retrieve the object is denied. + */ + public abstract Connectable getConnectable(UserContext userContext, + String identifier) throws GuacamoleException; + + /** + * Parses the given tunnel request type string, returning the + * TunnelRequestType which matches that string, as declared by + * {@link #PARAMETER_VALUE}. If no such type exists, null is returned. + * + * @param type + * The type string to parse. + * + * @return + * The TunnelRequestType which specifies the given string as its + * {@link #PARAMETER_VALUE}, or null if no such type exists. + */ + public static TunnelRequestType parseType(String type) { + + // Locate type with given parameter value + for (TunnelRequestType possibleType : values()) { + if (type.equals(possibleType.PARAMETER_VALUE)) + return possibleType; + } + + // No such type + return null; + + } + +} diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 45512086d..800eaf431 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -36,21 +36,22 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', var ManagedShareLink = $injector.get('ManagedShareLink'); // Required services - var $document = $injector.get('$document'); - var $q = $injector.get('$q'); - var $rootScope = $injector.get('$rootScope'); - var $window = $injector.get('$window'); - var authenticationService = $injector.get('authenticationService'); - var connectionGroupService = $injector.get('connectionGroupService'); - var connectionService = $injector.get('connectionService'); - var preferenceService = $injector.get('preferenceService'); - var requestService = $injector.get('requestService'); - var schemaService = $injector.get('schemaService'); - var tunnelService = $injector.get('tunnelService'); - var guacAudio = $injector.get('guacAudio'); - var guacHistory = $injector.get('guacHistory'); - var guacImage = $injector.get('guacImage'); - var guacVideo = $injector.get('guacVideo'); + var $document = $injector.get('$document'); + var $q = $injector.get('$q'); + var $rootScope = $injector.get('$rootScope'); + var $window = $injector.get('$window'); + var activeConnectionService = $injector.get('activeConnectionService'); + var authenticationService = $injector.get('authenticationService'); + var connectionGroupService = $injector.get('connectionGroupService'); + var connectionService = $injector.get('connectionService'); + var preferenceService = $injector.get('preferenceService'); + var requestService = $injector.get('requestService'); + var schemaService = $injector.get('schemaService'); + var tunnelService = $injector.get('tunnelService'); + var guacAudio = $injector.get('guacAudio'); + var guacHistory = $injector.get('guacHistory'); + var guacImage = $injector.get('guacImage'); + var guacVideo = $injector.get('guacVideo'); /** * The minimum amount of time to wait between updates to the client @@ -600,6 +601,29 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }, requestService.WARN); } + // If using an active connection, pull corresponding connection, then + // pull connection name and protocol information from that + else if (clientIdentifier.type === ClientIdentifier.Types.ACTIVE_CONNECTION) { + activeConnectionService.getActiveConnection(clientIdentifier.dataSource, clientIdentifier.id) + .then(function activeConnectionRetrieved(activeConnection) { + + // Attempt to retrieve connection details only if the + // underlying connection is known + if (activeConnection.connectionIdentifier) { + $q.all({ + connection : connectionService.getConnection(clientIdentifier.dataSource, activeConnection.connectionIdentifier), + protocols : schemaService.getProtocols(clientIdentifier.dataSource) + }) + .then(function dataRetrieved(values) { + managedClient.name = managedClient.title = values.connection.name; + managedClient.protocol = values.connection.protocol; + managedClient.forms = values.protocols[values.connection.protocol].connectionForms; + }, requestService.WARN); + } + + }, requestService.WARN); + } + return managedClient; }; diff --git a/guacamole/src/main/webapp/app/navigation/types/ClientIdentifier.js b/guacamole/src/main/webapp/app/navigation/types/ClientIdentifier.js index 23daef2cf..3f6892405 100644 --- a/guacamole/src/main/webapp/app/navigation/types/ClientIdentifier.js +++ b/guacamole/src/main/webapp/app/navigation/types/ClientIdentifier.js @@ -89,7 +89,14 @@ angular.module('client').factory('ClientIdentifier', ['$injector', * * @type String */ - CONNECTION_GROUP : 'g' + CONNECTION_GROUP : 'g', + + /** + * The type string for an active Guacamole connection. + * + * @type String + */ + ACTIVE_CONNECTION : 'a' }; diff --git a/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js b/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js index 5354bc1c8..3960b39d5 100644 --- a/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js +++ b/guacamole/src/main/webapp/app/rest/services/activeConnectionService.js @@ -29,6 +29,38 @@ angular.module('rest').factory('activeConnectionService', ['$injector', var service = {}; + /** + * Makes a request to the REST API to get a single active connection, + * returning a promise that provides the corresponding + * @link{ActiveConnection} if successful. + * + * @param {String} dataSource + * The identifier of the data source to retrieve the active connection + * from. + * + * @param {String} id + * The identifier of the active connection. + * + * @returns {Promise.} + * A promise which will resolve with a @link{ActiveConnection} upon + * success. + */ + service.getActiveConnection = function getActiveConnection(dataSource, id) { + + // Build HTTP parameters set + var httpParameters = { + token : authenticationService.getCurrentToken() + }; + + // Retrieve active connection + return requestService({ + method : 'GET', + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections/' + encodeURIComponent(id), + params : httpParameters + }); + + }; + /** * Makes a request to the REST API to get the list of active tunnels, * returning a promise that provides a map of @link{ActiveConnection} diff --git a/guacamole/src/main/webapp/app/rest/types/ActiveConnection.js b/guacamole/src/main/webapp/app/rest/types/ActiveConnection.js index 6c5aac211..dc0b140fe 100644 --- a/guacamole/src/main/webapp/app/rest/types/ActiveConnection.js +++ b/guacamole/src/main/webapp/app/rest/types/ActiveConnection.js @@ -76,6 +76,14 @@ angular.module('rest').factory('ActiveConnection', [function defineActiveConnect */ this.username = template.username; + /** + * Whether this active connection may be connected to, just as a + * normal connection. + * + * @type Boolean + */ + this.connectable = template.connectable; + }; return ActiveConnection; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js index 5e1774d05..30b8bdeb0 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js @@ -35,6 +35,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti // Required types var ActiveConnectionWrapper = $injector.get('ActiveConnectionWrapper'); + var ClientIdentifier = $injector.get('ClientIdentifier'); var ConnectionGroup = $injector.get('ConnectionGroup'); var SortOrder = $injector.get('SortOrder'); @@ -336,7 +337,37 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti 'actions' : [ DELETE_ACTION, CANCEL_ACTION] }); }; - + + /** + * Returns the relative URL of the client page which accesses the + * given active connection. If the active connection is not + * connectable, null is returned. + * + * @param {String} dataSource + * The unique identifier of the data source containing the + * active connection. + * + * @param {String} activeConnection + * The active connection to determine the relative URL of. + * + * @returns {String} + * The relative URL of the client page which accesses the given + * active connection, or null if the active connection is not + * connectable. + */ + $scope.getClientURL = function getClientURL(dataSource, activeConnection) { + + if (!activeConnection.connectable) + return null; + + return '#/client/' + encodeURIComponent(ClientIdentifier.toString({ + dataSource : dataSource, + type : ClientIdentifier.Types.ACTIVE_CONNECTION, + id : activeConnection.identifier + })); + + }; + /** * Returns whether the selected sessions can be deleted. * diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html b/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html index 184ff0b10..698582cc7 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsSessions.html @@ -40,7 +40,9 @@ {{wrapper.startDate}} {{wrapper.activeConnection.remoteHost}} - {{wrapper.name}} + {{wrapper.name}}