diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java index 0816ae2c5..dab83953e 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java @@ -115,40 +115,6 @@ public class GuacamoleSession { this.authenticatedUser = authenticatedUser; } - /** - * Returns the UserContext associated with this session. - * - * @return The UserContext associated with this session. - */ - public UserContext getUserContext() { - - // Warn of deprecation - logger.debug( - "\n****************************************************************" - + "\n" - + "\n !!!! PLEASE DO NOT USE getUserContext() !!!!" - + "\n" - + "\n getUserContext() has been replaced by getUserContexts(), which" - + "\n properly handles multiple authentication providers. All use of" - + "\n the old getUserContext() must be removed before GUAC-586 can" - + "\n be considered complete." - + "\n" - + "\n****************************************************************" - ); - - // Return the UserContext associated with the AuthenticationProvider - // that authenticated the current user. - String authProviderIdentifier = authenticatedUser.getAuthenticationProvider().getIdentifier(); - for (UserContext userContext : userContexts) { - if (userContext.getAuthenticationProvider().getIdentifier().equals(authProviderIdentifier)) - return userContext; - } - - // If not found, return null - return null; - - } - /** * Returns a list of all UserContexts associated with this session. Each * AuthenticationProvider currently loaded by Guacamole may provide its own diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/HTTPTunnelRequest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/HTTPTunnelRequest.java index e36d7e6e3..3aab28006 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/HTTPTunnelRequest.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/HTTPTunnelRequest.java @@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletRequest; * * @author Michael Jumper */ -public class HTTPTunnelRequest implements TunnelRequest { +public class HTTPTunnelRequest extends TunnelRequest { /** * The wrapped HttpServletRequest. diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequest.java index b172cb107..a18375a71 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequest.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Glyptodon LLC + * 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 @@ -23,88 +23,331 @@ package org.glyptodon.guacamole.net.basic; import java.util.List; +import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleException; /** - * Request interface which provides only the functions absolutely required - * to retrieve and connect to a tunnel. + * A request object which provides only the functions absolutely required to + * retrieve and connect to a tunnel. * * @author Michael Jumper */ -public interface TunnelRequest { +public abstract class TunnelRequest { /** - * All supported identifier types. + * The name of the request parameter containing the user's authentication + * token. */ - public static enum IdentifierType { + public static final String AUTH_TOKEN_PARAMETER = "token"; + + /** + * The name of the parameter containing the identifier of the + * AuthenticationProvider associated with the UserContext containing the + * object to which a tunnel is being requested. + */ + public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE"; + + /** + * The name of the parameter specifying the type of object to which a + * tunnel is being requested. Currently, this may be "c" for a Guacamole + * connection, or "g" for a Guacamole connection group. + */ + public static final String TYPE_PARAMETER = "GUAC_TYPE"; + + /** + * The name of the parameter containing the unique identifier of the object + * to which a tunnel is being requested. + */ + public static final String IDENTIFIER_PARAMETER = "GUAC_ID"; + + /** + * The name of the parameter containing the desired display width, in + * pixels. + */ + public static final String WIDTH_PARAMETER = "GUAC_WIDTH"; + + /** + * The name of the parameter containing the desired display height, in + * pixels. + */ + public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT"; + + /** + * The name of the parameter containing the desired display resolution, in + * DPI. + */ + public static final String DPI_PARAMETER = "GUAC_DPI"; + + /** + * The name of the parameter specifying one supported audio mimetype. This + * will normally appear multiple times within a single tunnel request - + * once for each mimetype. + */ + public static final String AUDIO_PARAMETER = "GUAC_AUDIO"; + + /** + * The name of the parameter specifying one supported video mimetype. This + * will normally appear multiple times within a single tunnel request - + * once for each mimetype. + */ + public static final String VIDEO_PARAMETER = "GUAC_VIDEO"; + + /** + * All supported object types that can be used as the destination of a + * tunnel. + */ + public static enum Type { /** - * The unique identifier of a connection. + * A Guacamole connection. */ - CONNECTION("c/"), + CONNECTION("c"), /** - * The unique identifier of a connection group. + * A Guacamole connection group. */ - CONNECTION_GROUP("g/"); + CONNECTION_GROUP("g"); + + /** + * The parameter value which denotes a destination object of this type. + */ + final String PARAMETER_VALUE; /** - * The prefix which precedes an identifier of this type. + * Defines a Type having the given corresponding parameter value. + * + * @param value + * The parameter value which denotes a destination object of this + * type. */ - final String PREFIX; - - /** - * Defines an IdentifierType having the given prefix. - * @param prefix The prefix which will precede any identifier of this - * type, thus differentiating it from other identifier - * types. - */ - IdentifierType(String prefix) { - PREFIX = prefix; + Type(String value) { + PARAMETER_VALUE = value; } - /** - * Given an identifier, determines the corresponding identifier type. - * - * @param identifier The identifier whose type should be identified. - * @return The identified identifier type. - */ - static IdentifierType getType(String identifier) { - - // If null, no known identifier - if (identifier == null) - return null; - - // Connection identifiers - if (identifier.startsWith(CONNECTION.PREFIX)) - return CONNECTION; - - // Connection group identifiers - if (identifier.startsWith(CONNECTION_GROUP.PREFIX)) - return CONNECTION_GROUP; - - // Otherwise, unknown - return null; - - } - }; /** * Returns the value of the parameter having the given name. * - * @param name The name of the parameter to return. - * @return The value of the parameter having the given name, or null - * if no such parameter was specified. + * @param name + * The name of the parameter to return. + * + * @return + * The value of the parameter having the given name, or null if no such + * parameter was specified. */ - public String getParameter(String name); + public abstract String getParameter(String name); /** * Returns a list of all values specified for the given parameter. * - * @param name The name of the parameter to return. - * @return All values of the parameter having the given name , or null - * if no such parameter was specified. + * @param name + * The name of the parameter to return. + * + * @return + * All values of the parameter having the given name , or null if no + * such parameter was specified. */ - public List getParameterValues(String name); - + public abstract List getParameterValues(String name); + + /** + * Returns the value of the parameter having the given name, throwing an + * exception if the parameter is missing. + * + * @param name + * The name of the parameter to return. + * + * @return + * The value of the parameter having the given name. + * + * @throws GuacamoleException + * If the parameter is not present in the request. + */ + public String getRequiredParameter(String name) throws GuacamoleException { + + // Pull requested parameter, aborting if absent + String value = getParameter(name); + if (value == null) + throw new GuacamoleClientException("Parameter \"" + name + "\" is required."); + + return value; + + } + + /** + * Returns the integer value of the parameter having the given name, + * throwing an exception if the parameter cannot be parsed. + * + * @param name + * The name of the parameter to return. + * + * @return + * The integer value of the parameter having the given name, or null if + * the parameter is missing. + * + * @throws GuacamoleException + * If the parameter is not a valid integer. + */ + public Integer getIntegerParameter(String name) throws GuacamoleException { + + // Pull requested parameter + String value = getParameter(name); + if (value == null) + return null; + + // Attempt to parse as an integer + try { + return Integer.parseInt(value); + } + + // Rethrow any parsing error as a GuacamoleClientException + catch (NumberFormatException e) { + throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e); + } + + } + + /** + * Returns the authentication token associated with this tunnel request. + * + * @return + * The authentication token associated with this tunnel request, or + * null if no authentication token is present. + */ + public String getAuthenticationToken() { + return getParameter(AUTH_TOKEN_PARAMETER); + } + + /** + * Returns the identifier of the AuthenticationProvider associated with the + * UserContext from which the connection or connection group is to be + * retrieved when the tunnel is created. In the context of the REST API and + * the JavaScript side of the web application, this is referred to as the + * data source identifier. + * + * @return + * The identifier of the AuthenticationProvider associated with the + * UserContext from which the connection or connection group is to be + * retrieved when the tunnel is created. + * + * @throws GuacamoleException + * If the identifier was not present in the request. + */ + public String getAuthenticationProviderIdentifier() + throws GuacamoleException { + return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER); + } + + /** + * Returns the type of object for which the tunnel is being requested. + * + * @return + * The type of object for which the tunnel is being requested. + * + * @throws GuacamoleException + * If the type was not present in the request, or if the type requested + * is in the wrong format. + */ + public Type 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; + + } + + throw new GuacamoleClientException("Illegal identifier - unknown type."); + + } + + /** + * Returns the identifier of the destination of the tunnel being requested. + * As there are multiple types of destination objects available, and within + * multiple data sources, the associated object type and data source are + * also necessary to determine what this identifier refers to. + * + * @return + * The identifier of the destination of the tunnel being requested. + * + * @throws GuacamoleException + * If the identifier was not present in the request. + */ + public String getIdentifier() throws GuacamoleException { + return getRequiredParameter(IDENTIFIER_PARAMETER); + } + + /** + * Returns the display width desired for the Guacamole session over the + * tunnel being requested. + * + * @return + * The display width desired for the Guacamole session over the tunnel + * being requested, or null if no width was given. + * + * @throws GuacamoleException + * If the width specified was not a valid integer. + */ + public Integer getWidth() throws GuacamoleException { + return getIntegerParameter(WIDTH_PARAMETER); + } + + /** + * Returns the display height desired for the Guacamole session over the + * tunnel being requested. + * + * @return + * The display height desired for the Guacamole session over the tunnel + * being requested, or null if no width was given. + * + * @throws GuacamoleException + * If the height specified was not a valid integer. + */ + public Integer getHeight() throws GuacamoleException { + return getIntegerParameter(HEIGHT_PARAMETER); + } + + /** + * Returns the display resolution desired for the Guacamole session over + * the tunnel being requested, in DPI. + * + * @return + * The display resolution desired for the Guacamole session over the + * tunnel being requested, or null if no resolution was given. + * + * @throws GuacamoleException + * If the resolution specified was not a valid integer. + */ + public Integer getDPI() throws GuacamoleException { + return getIntegerParameter(DPI_PARAMETER); + } + + /** + * Returns a list of all audio mimetypes declared as supported within the + * tunnel request. + * + * @return + * A list of all audio mimetypes declared as supported within the + * tunnel request, or null if no mimetypes were specified. + */ + public List getAudioMimetypes() { + return getParameterValues(AUDIO_PARAMETER); + } + + /** + * Returns a list of all video mimetypes declared as supported within the + * tunnel request. + * + * @return + * A list of all video mimetypes declared as supported within the + * tunnel request, or null if no mimetypes were specified. + */ + public List getVideoMimetypes() { + return getParameterValues(VIDEO_PARAMETER); + } + } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java index 9c20eedeb..5953a31ed 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java @@ -25,16 +25,15 @@ package org.glyptodon.guacamole.net.basic; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.List; -import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleSecurityException; -import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel; import org.glyptodon.guacamole.net.GuacamoleTunnel; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; import org.glyptodon.guacamole.protocol.GuacamoleClientInformation; import org.slf4j.Logger; @@ -53,12 +52,6 @@ import org.slf4j.LoggerFactory; @Singleton public class TunnelRequestService { - /** - * The Guacamole server environment. - */ - @Inject - private Environment environment; - /** * Logger for this class. */ @@ -70,6 +63,12 @@ public class TunnelRequestService { @Inject private AuthenticationService authenticationService; + /** + * Service for convenient retrieval of objects. + */ + @Inject + private ObjectRetrievalService retrievalService; + /** * Reads and returns the client information provided within the given * request. @@ -80,35 +79,40 @@ public class TunnelRequestService { * @return GuacamoleClientInformation * An object containing information about the client sending the tunnel * request. + * + * @throws GuacamoleException + * If the parameters of the tunnel request are invalid. */ - protected GuacamoleClientInformation getClientInformation(TunnelRequest request) { + protected GuacamoleClientInformation getClientInformation(TunnelRequest request) + throws GuacamoleException { + // Get client information GuacamoleClientInformation info = new GuacamoleClientInformation(); // Set width if provided - String width = request.getParameter("width"); + Integer width = request.getWidth(); if (width != null) - info.setOptimalScreenWidth(Integer.parseInt(width)); + info.setOptimalScreenWidth(width); // Set height if provided - String height = request.getParameter("height"); + Integer height = request.getHeight(); if (height != null) - info.setOptimalScreenHeight(Integer.parseInt(height)); + info.setOptimalScreenHeight(height); // Set resolution if provided - String dpi = request.getParameter("dpi"); + Integer dpi = request.getDPI(); if (dpi != null) - info.setOptimalResolution(Integer.parseInt(dpi)); + info.setOptimalResolution(dpi); // Add audio mimetypes - List audio_mimetypes = request.getParameterValues("audio"); - if (audio_mimetypes != null) - info.getAudioMimetypes().addAll(audio_mimetypes); + List audioMimetypes = request.getAudioMimetypes(); + if (audioMimetypes != null) + info.getAudioMimetypes().addAll(audioMimetypes); // Add video mimetypes - List video_mimetypes = request.getParameterValues("video"); - if (video_mimetypes != null) - info.getVideoMimetypes().addAll(video_mimetypes); + List videoMimetypes = request.getVideoMimetypes(); + if (videoMimetypes != null) + info.getVideoMimetypes().addAll(videoMimetypes); return info; } @@ -122,7 +126,7 @@ public class TunnelRequestService { * The UserContext associated with the user for whom the tunnel is * being created. * - * @param idType + * @param type * The type of object being connected to (connection or group). * * @param id @@ -138,13 +142,13 @@ public class TunnelRequestService { * If an error occurs while creating the tunnel. */ protected GuacamoleTunnel createConnectedTunnel(UserContext context, - final TunnelRequest.IdentifierType idType, String id, + final TunnelRequest.Type type, String id, GuacamoleClientInformation info) throws GuacamoleException { // Create connected tunnel from identifier GuacamoleTunnel tunnel = null; - switch (idType) { + switch (type) { // Connection identifiers case CONNECTION: { @@ -205,7 +209,7 @@ public class TunnelRequestService { * @param session * The Guacamole session to associate the tunnel with. * - * @param idType + * @param type * The type of object being connected to (connection or group). * * @param id @@ -220,7 +224,7 @@ public class TunnelRequestService { * If an error occurs while obtaining the tunnel. */ protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleSession session, - GuacamoleTunnel tunnel, final TunnelRequest.IdentifierType idType, + GuacamoleTunnel tunnel, final TunnelRequest.Type type, final String id) throws GuacamoleException { // Monitor tunnel closure and data @@ -239,7 +243,7 @@ public class TunnelRequestService { long duration = connectionEndTime - connectionStartTime; // Log closure - switch (idType) { + switch (type) { // Connection identifiers case CONNECTION: @@ -289,27 +293,20 @@ public class TunnelRequestService { public GuacamoleTunnel createTunnel(TunnelRequest request) throws GuacamoleException { - // Get auth token and session - final String authToken = request.getParameter("authToken"); - final GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); - - // Get client information and connection ID from request - String id = request.getParameter("id"); - final GuacamoleClientInformation info = getClientInformation(request); - - // Determine ID type - TunnelRequest.IdentifierType idType = TunnelRequest.IdentifierType.getType(id); - if (idType == null) - throw new GuacamoleClientException("Illegal identifier - unknown type."); - - // Remove prefix - id = id.substring(idType.PREFIX.length()); + // Parse request parameters + String authToken = request.getAuthenticationToken(); + String id = request.getIdentifier(); + TunnelRequest.Type type = request.getType(); + String authProviderIdentifier = request.getAuthenticationProviderIdentifier(); + GuacamoleClientInformation info = getClientInformation(request); // Create connected tunnel using provided connection ID and client information - final GuacamoleTunnel tunnel = createConnectedTunnel(session.getUserContext(), idType, id, info); + GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); + UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier); + GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info); // Associate tunnel with session - return createAssociatedTunnel(session, tunnel, idType, id); + return createAssociatedTunnel(session, tunnel, type, id); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java index b730a7163..7667bb43c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java @@ -66,19 +66,6 @@ public class AuthenticationService { } - /** - * Finds the UserContext for a given auth token, if the auth token represents - * a currently logged in user. Throws an unauthorized error otherwise. - * - * @param authToken The auth token to check against the map of logged in users. - * @return The user context that corresponds to the provided auth token. - * @throws GuacamoleException If the auth token does not correspond to any - * logged in user. - */ - public UserContext getUserContext(String authToken) throws GuacamoleException { - return getGuacamoleSession(authToken).getUserContext(); - } - /** * Returns all UserContexts associated with a given auth token, if the auth * token represents a currently logged in user. Throws an unauthorized diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelRequest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelRequest.java index cf6922157..6a687f8f0 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelRequest.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelRequest.java @@ -32,7 +32,7 @@ import org.glyptodon.guacamole.net.basic.TunnelRequest; * * @author Michael Jumper */ -public class WebSocketTunnelRequest implements TunnelRequest { +public class WebSocketTunnelRequest extends TunnelRequest { /** * All parameters passed via HTTP to the WebSocket handshake. diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java index 1c4c96bd8..5e71b3824 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java @@ -33,7 +33,7 @@ import org.glyptodon.guacamole.net.basic.TunnelRequest; * * @author Michael Jumper */ -public class WebSocketTunnelRequest implements TunnelRequest { +public class WebSocketTunnelRequest extends TunnelRequest { /** * All parameters passed via HTTP to the WebSocket handshake. diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 692051bc1..2fab8e37b 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -158,7 +158,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams var RECONNECT_ACTION = { name : "CLIENT.ACTION_RECONNECT", callback : function reconnectCallback() { - $scope.client = guacClientManager.replaceManagedClient(uniqueId, $routeParams.params); + $scope.client = guacClientManager.replaceManagedClient($routeParams.id, $routeParams.params); guacNotification.showStatus(false); } }; @@ -219,13 +219,13 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams $scope.$on('guacClientClipboard', function clientClipboardListener(event, client, mimetype, clipboardData) { $scope.clipboardData = clipboardData; }); - - /* - * Parse the type, name, and id out of the url paramteres, - * as well as any extra parameters if set. + + /** + * The client which should be attached to the client UI. + * + * @type ManagedClient */ - var uniqueId = $routeParams.type + '/' + $routeParams.id; - $scope.client = guacClientManager.getManagedClient(uniqueId, $routeParams.params); + $scope.client = guacClientManager.getManagedClient($routeParams.id, $routeParams.params); var keysCurrentlyPressed = {}; diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 822a80870..c2a3bf035 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -28,6 +28,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', // Required types var ClientProperties = $injector.get('ClientProperties'); + var ClientIdentifier = $injector.get('ClientIdentifier'); var ManagedClientState = $injector.get('ManagedClientState'); var ManagedDisplay = $injector.get('ManagedDisplay'); var ManagedFileDownload = $injector.get('ManagedFileDownload'); @@ -153,8 +154,8 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', * desired connection ID, display resolution, and supported audio/video * codecs. * - * @param {String} id - * The ID of the connection or group to connect to. + * @param {ClientIdentifier} identifier + * The identifier representing the connection or group to connect to. * * @param {String} [connectionParameters] * Any additional HTTP parameters to pass while connecting. @@ -163,7 +164,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', * The string of connection parameters to be passed to the Guacamole * client. */ - var getConnectString = function getConnectString(id, connectionParameters) { + var getConnectString = function getConnectString(identifier, connectionParameters) { // Calculate optimal width/height for display var pixel_density = $window.devicePixelRatio || 1; @@ -173,21 +174,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', // Build base connect string var connectString = - "id=" + encodeURIComponent(id) - + "&authToken=" + encodeURIComponent(authenticationService.getCurrentToken()) - + "&width=" + Math.floor(optimal_width) - + "&height=" + Math.floor(optimal_height) - + "&dpi=" + Math.floor(optimal_dpi) + "token=" + encodeURIComponent(authenticationService.getCurrentToken()) + + "&GUAC_DATA_SOURCE=" + encodeURIComponent(identifier.dataSource) + + "&GUAC_ID=" + encodeURIComponent(identifier.id) + + "&GUAC_TYPE=" + encodeURIComponent(identifier.type) + + "&GUAC_WIDTH=" + Math.floor(optimal_width) + + "&GUAC_HEIGHT=" + Math.floor(optimal_height) + + "&GUAC_DPI=" + Math.floor(optimal_dpi) + (connectionParameters ? '&' + connectionParameters : ''); // Add audio mimetypes to connect_string guacAudio.supported.forEach(function(mimetype) { - connectString += "&audio=" + encodeURIComponent(mimetype); + connectString += "&GUAC_AUDIO=" + encodeURIComponent(mimetype); }); // Add video mimetypes to connect_string guacVideo.supported.forEach(function(mimetype) { - connectString += "&video=" + encodeURIComponent(mimetype); + connectString += "&GUAC_VIDEO=" + encodeURIComponent(mimetype); }); return connectString; @@ -238,7 +241,9 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', * or group. * * @param {String} id - * The ID of the connection or group to connect to. + * The ID of the connection or group to connect to. This String must be + * a valid ClientIdentifier string, as would be generated by + * ClientIdentifier.toString(). * * @param {String} [connectionParameters] * Any additional HTTP parameters to pass while connecting. @@ -402,23 +407,23 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', // Manage the client display managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); - // Connect the Guacamole client - client.connect(getConnectString(id, connectionParameters)); + // Parse connection details from ID + var clientIdentifier = ClientIdentifier.fromString(id); - // Determine type of connection - var typePrefix = id.substring(0, 2); + // Connect the Guacamole client + client.connect(getConnectString(clientIdentifier, connectionParameters)); // If using a connection, pull connection name - if (typePrefix === 'c/') { - connectionService.getConnection(id.substring(2)) + if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION) { + connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id) .success(function connectionRetrieved(connection) { managedClient.name = connection.name; }); } // If using a connection group, pull connection name - else if (typePrefix === 'g/') { - connectionGroupService.getConnectionGroup(id.substring(2)) + else if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION_GROUP) { + connectionGroupService.getConnectionGroup(clientIdentifier.dataSource, clientIdentifier.id) .success(function connectionGroupRetrieved(group) { managedClient.name = group.name; }); diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index 1e0573ebc..12eb1c25e 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -27,7 +27,8 @@ angular.module('home').controller('homeController', ['$scope', '$injector', function homeController($scope, $injector) { // Get required types - var ConnectionGroup = $injector.get('ConnectionGroup'); + var ConnectionGroup = $injector.get('ConnectionGroup'); + var ClientIdentifier = $injector.get('ClientIdentifier'); // Get required services var authenticationService = $injector.get('authenticationService'); @@ -56,6 +57,51 @@ angular.module('home').controller('homeController', ['$scope', '$injector', }; + /** + * Object passed to the guacGroupList directive, providing context-specific + * functions or data. + */ + $scope.context = { + + /** + * Returns the unique string identifier which must be used when + * connecting to a connection or connection group represented by the + * given GroupListItem. + * + * @param {GroupListItem} item + * The GroupListItem to determine the client identifier of. + * + * @returns {String} + * The client identifier associated with the connection or + * connection group represented by the given GroupListItem, or null + * if the GroupListItem cannot have an associated client + * identifier. + */ + getClientIdentifier : function getClientIdentifier(item) { + + // If the item is a connection, generate a connection identifier + if (item.isConnection) + return ClientIdentifier.toString({ + dataSource : item.dataSource, + type : ClientIdentifier.Types.CONNECTION, + id : item.identifier + }); + + // If the item is a connection, generate a connection group identifier + if (item.isConnectionGroup) + return ClientIdentifier.toString({ + dataSource : item.dataSource, + type : ClientIdentifier.Types.CONNECTION_GROUP, + id : item.identifier + }); + + // Otherwise, no such identifier can exist + return null; + + } + + }; + // Retrieve root groups and all descendants dataSourceService.apply( connectionGroupService.getConnectionGroupTree, diff --git a/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js b/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js index c6e8443ba..a5d913a02 100644 --- a/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js +++ b/guacamole/src/main/webapp/app/home/directives/guacRecentConnections.js @@ -48,6 +48,7 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo // Required types var ActiveConnection = $injector.get('ActiveConnection'); + var ClientIdentifier = $injector.get('ClientIdentifier'); var RecentConnection = $injector.get('RecentConnection'); // Required services @@ -104,7 +105,11 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo var addVisibleConnection = function addVisibleConnection(dataSource, connection) { // Add given connection to set of visible objects - visibleObjects['c/' + connection.identifier] = connection; + visibleObjects[ClientIdentifier.toString({ + dataSource : dataSource, + type : ClientIdentifier.Types.CONNECTION, + id : connection.identifier + })] = connection; }; @@ -123,7 +128,11 @@ angular.module('home').directive('guacRecentConnections', [function guacRecentCo var addVisibleConnectionGroup = function addVisibleConnectionGroup(dataSource, connectionGroup) { // Add given connection group to set of visible objects - visibleObjects['g/' + connectionGroup.identifier] = connectionGroup; + visibleObjects[ClientIdentifier.toString({ + dataSource : dataSource, + type : ClientIdentifier.Types.CONNECTION_GROUP, + id : connectionGroup.identifier + })] = connectionGroup; // Add all child connections if (connectionGroup.childConnections) diff --git a/guacamole/src/main/webapp/app/home/templates/connection.html b/guacamole/src/main/webapp/app/home/templates/connection.html index 33c5aec7e..87f101af7 100644 --- a/guacamole/src/main/webapp/app/home/templates/connection.html +++ b/guacamole/src/main/webapp/app/home/templates/connection.html @@ -1,4 +1,4 @@ - + - {{item.name}} + {{item.name}} {{item.name}} diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html index c5e2df696..a2c362ac5 100644 --- a/guacamole/src/main/webapp/app/home/templates/home.html +++ b/guacamole/src/main/webapp/app/home/templates/home.html @@ -37,6 +37,7 @@

{{'HOME.SECTION_HEADER_ALL_CONNECTIONS' | translate}}

+ */ + ClientIdentifier.Types = { + + /** + * The type string for a Guacamole connection. + * + * @type String + */ + CONNECTION : 'c', + + /** + * The type string for a Guacamole connection group. + * + * @type String + */ + CONNECTION_GROUP : 'g' + + }; + + /** + * Converts the given ClientIdentifier or ClientIdentifier-like object to + * a String representation. Any object having the same properties as + * ClientIdentifier may be used, but only those properties will be taken + * into account when producing the resulting String. + * + * @param {ClientIdentifier|Object} id + * The ClientIdentifier or ClientIdentifier-like object to convert to + * a String representation. + * + * @returns {String} + * A deterministic String representation of the given ClientIdentifier + * or ClientIdentifier-like object. + */ + ClientIdentifier.toString = function toString(id) { + return $window.btoa([ + id.id, + id.type, + id.dataSource + ].join('\0')); + }; + + /** + * Converts the given String into the corresponding ClientIdentifier. If + * the provided String is not a valid identifier, it will be interpreted + * as the identifier of a connection within the data source that + * authenticated the current user. + * + * @param {String} str + * The String to convert to a ClientIdentifier. + * + * @returns {ClientIdentifier} + * The ClientIdentifier represented by the given String. + */ + ClientIdentifier.fromString = function fromString(str) { + + try { + var values = $window.atob(str).split('\0'); + return new ClientIdentifier({ + id : values[0], + type : values[1], + dataSource : values[2] + }); + } + + // If the provided string is invalid, transform into a reasonable guess + catch (e) { + return new ClientIdentifier({ + id : str, + type : ClientIdentifier.Types.CONNECTION, + dataSource : authenticationService.getDataSource() || 'default' + }); + } + + }; + + return ClientIdentifier; + +}]);