diff --git a/guacamole-common-js/src/main/webapp/modules/Tunnel.js b/guacamole-common-js/src/main/webapp/modules/Tunnel.js index 49129888e..eef12b58c 100644 --- a/guacamole-common-js/src/main/webapp/modules/Tunnel.js +++ b/guacamole-common-js/src/main/webapp/modules/Tunnel.js @@ -309,6 +309,25 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) { */ var extraHeaders = extraTunnelHeaders || {}; + /** + * The name of the HTTP header containing the session token specific to the + * HTTP tunnel implementation. + * + * @private + * @constant + * @type {string} + */ + var TUNNEL_TOKEN_HEADER = 'Guacamole-Tunnel-Token'; + + /** + * The session token currently assigned to this HTTP tunnel. All distinct + * HTTP tunnel connections will have their own dedicated session token. + * + * @private + * @type {string} + */ + var tunnelSessionToken = null; + /** * Adds the configured additional headers to the given request. * @@ -453,6 +472,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) { message_xmlhttprequest.withCredentials = withCredentials; addExtraHeaders(message_xmlhttprequest, extraHeaders); message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream"); + message_xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken); // Once response received, send next queued event. message_xmlhttprequest.onreadystatechange = function() { @@ -697,6 +717,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) { // Make request, increment request ID var xmlhttprequest = new XMLHttpRequest(); xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + (request_id++)); + xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken); xmlhttprequest.withCredentials = withCredentials; addExtraHeaders(xmlhttprequest, extraHeaders); xmlhttprequest.send(null); @@ -728,8 +749,15 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) { reset_timeout(); - // Get UUID from response + // Get UUID and HTTP-specific tunnel session token from response tunnel.setUUID(connect_xmlhttprequest.responseText); + tunnelSessionToken = connect_xmlhttprequest.getResponseHeader(TUNNEL_TOKEN_HEADER); + + // Fail connect attempt if token is not successfully assigned + if (!tunnelSessionToken) { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND)); + return; + } // Mark as open tunnel.setState(Guacamole.Tunnel.State.OPEN); diff --git a/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelMap.java b/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelMap.java index eaa2fcf1c..918ca206b 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelMap.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelMap.java @@ -58,7 +58,8 @@ class GuacamoleHTTPTunnelMap { private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); /** - * Map of all tunnels that are using HTTP, indexed by tunnel UUID. + * Map of all tunnels that are using HTTP, indexed by their tunnel-specific + * session tokens. */ private final ConcurrentMap tunnelMap = new ConcurrentHashMap(); @@ -141,22 +142,22 @@ class GuacamoleHTTPTunnelMap { } /** - * Returns the GuacamoleTunnel having the given UUID, wrapped within a - * GuacamoleHTTPTunnel. If the no tunnel having the given UUID is - * available, null is returned. + * Returns the GuacamoleTunnel associated with the given tunnel-specific + * session token, wrapped within a GuacamoleHTTPTunnel. If the no tunnel + * is associated with the given token, null is returned. * - * @param uuid - * The UUID of the tunnel to retrieve. + * @param tunnelSessionToken + * The tunnel-specific session token of the HTTP tunnel to retrieve. * * @return - * The GuacamoleTunnel having the given UUID, wrapped within a - * GuacamoleHTTPTunnel, if such a tunnel exists, or null if there is no - * such tunnel. + * The GuacamoleTunnel associated with the given tunnel-specific + * session token, wrapped within a GuacamoleHTTPTunnel, if such a + * tunnel exists, or null if there is no such tunnel. */ - public GuacamoleHTTPTunnel get(String uuid) { + public GuacamoleHTTPTunnel get(String tunnelSessionToken) { // Update the last access time - GuacamoleHTTPTunnel tunnel = tunnelMap.get(uuid); + GuacamoleHTTPTunnel tunnel = tunnelMap.get(tunnelSessionToken); if (tunnel != null) tunnel.access(); @@ -169,32 +170,34 @@ class GuacamoleHTTPTunnelMap { * Registers that a new connection has been established using HTTP via the * given GuacamoleTunnel. * - * @param uuid - * The UUID of the tunnel being added (registered). + * @param tunnelSessionToken + * The tunnel-specific session token of the HTTP tunnel being added + * (registered). * * @param tunnel * The GuacamoleTunnel being registered, its associated connection * having just been established via HTTP. */ - public void put(String uuid, GuacamoleTunnel tunnel) { - tunnelMap.put(uuid, new GuacamoleHTTPTunnel(tunnel)); + public void put(String tunnelSessionToken, GuacamoleTunnel tunnel) { + tunnelMap.put(tunnelSessionToken, new GuacamoleHTTPTunnel(tunnel)); } /** - * Removes the GuacamoleTunnel having the given UUID, if such a tunnel - * exists. The original tunnel is returned wrapped within a - * GuacamoleHTTPTunnel. + * Removes the GuacamoleTunnel associated with the given tunnel-specific + * session token, if such a tunnel exists. The original tunnel is returned + * wrapped within a GuacamoleHTTPTunnel. * - * @param uuid - * The UUID of the tunnel to remove (deregister). + * @param tunnelSessionToken + * The tunnel-specific session token of the HTTP tunnel to remove + * (deregister). * * @return - * The GuacamoleTunnel having the given UUID, if such a tunnel exists, - * wrapped within a GuacamoleHTTPTunnel, or null if no such tunnel - * exists and no removal was performed. + * The GuacamoleTunnel having the given tunnel-specific session token, + * if such a tunnel exists, wrapped within a GuacamoleHTTPTunnel, or + * null if no such tunnel exists and no removal was performed. */ - public GuacamoleHTTPTunnel remove(String uuid) { - return tunnelMap.remove(uuid); + public GuacamoleHTTPTunnel remove(String tunnelSessionToken) { + return tunnelMap.remove(tunnelSessionToken); } /** diff --git a/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java b/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java index be2da1312..492076933 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/servlet/GuacamoleHTTPTunnelServlet.java @@ -25,6 +25,8 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; +import java.security.SecureRandom; +import java.util.Base64; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -37,7 +39,6 @@ import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.io.GuacamoleReader; import org.apache.guacamole.io.GuacamoleWriter; import org.apache.guacamole.net.GuacamoleTunnel; -import org.apache.guacamole.protocol.GuacamoleStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,10 +54,17 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelServlet.class); /** - * Map of absolutely all active tunnels using HTTP, indexed by tunnel UUID. + * Map of absolutely all active tunnels using HTTP, indexed by tunnel + * session token. */ private final GuacamoleHTTPTunnelMap tunnels = new GuacamoleHTTPTunnelMap(); + /** + * The name of the HTTP header that contains the tunnel-specific session + * token identifying each active and distinct HTTP tunnel connection. + */ + private static final String TUNNEL_TOKEN_HEADER_NAME = "Guacamole-Tunnel-Token"; + /** * The prefix of the query string which denotes a tunnel read operation. */ @@ -68,29 +76,64 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { private static final String WRITE_PREFIX = "write:"; /** - * The length of the read prefix, in characters. + * Instance of SecureRandom for generating the session token specific to + * each distinct HTTP tunnel connection. */ - private static final int READ_PREFIX_LENGTH = READ_PREFIX.length(); + private final SecureRandom secureRandom = new SecureRandom(); /** - * The length of the write prefix, in characters. + * Instance of Base64.Encoder for encoding random session tokens as + * strings. */ - private static final int WRITE_PREFIX_LENGTH = WRITE_PREFIX.length(); + private final Base64.Encoder encoder = Base64.getEncoder(); /** - * The length of every tunnel UUID, in characters. + * Generates a new, securely-random session token that may be used to + * represent the ongoing communication session of a distinct HTTP tunnel + * connection. + * + * @return + * A new, securely-random session token. */ - private static final int UUID_LENGTH = 36; + protected String generateToken() { + byte[] bytes = new byte[33]; + secureRandom.nextBytes(bytes); + return encoder.encodeToString(bytes); + } /** * Registers the given tunnel such that future read/write requests to that * tunnel will be properly directed. * + * @deprecated + * This function has been deprecated in favor of {@link #registerTunnel(java.lang.String, org.apache.guacamole.net.GuacamoleTunnel)}, + * which decouples identification of HTTP tunnel sessions from the + * tunnel UUID. + * * @param tunnel * The tunnel to register. */ + @Deprecated protected void registerTunnel(GuacamoleTunnel tunnel) { - tunnels.put(tunnel.getUUID().toString(), tunnel); + registerTunnel(tunnel.getUUID().toString(), tunnel); + } + + /** + * Registers the given HTTP tunnel such that future read/write requests + * including the given tunnel-specific session token will be properly + * directed. The session token must be unpredictable (securely-random) and + * unique across all active HTTP tunnels. It is recommended that each HTTP + * tunnel session token be obtained through calling {@link #generateToken()}. + * + * @param tunnelSessionToken + * The tunnel-specific session token to associate with the HTTP tunnel + * being registered. + * + * @param tunnel + * The tunnel to register. + */ + protected void registerTunnel(String tunnelSessionToken, GuacamoleTunnel tunnel) { + tunnels.put(tunnelSessionToken, tunnel); logger.debug("Registered tunnel \"{}\".", tunnel.getUUID()); } @@ -98,33 +141,56 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { * Deregisters the given tunnel such that future read/write requests to * that tunnel will be rejected. * + * @deprecated + * This function has been deprecated in favor of {@link #deregisterTunnel(java.lang.String)}, + * which decouples identification of HTTP tunnel sessions from the + * tunnel UUID. + * * @param tunnel * The tunnel to deregister. */ + @Deprecated protected void deregisterTunnel(GuacamoleTunnel tunnel) { - tunnels.remove(tunnel.getUUID().toString()); - logger.debug("Deregistered tunnel \"{}\".", tunnel.getUUID()); + deregisterTunnel(tunnel.getUUID().toString()); } /** - * Returns the tunnel with the given UUID, if it has been registered with - * registerTunnel() and not yet deregistered with deregisterTunnel(). + * Deregisters the HTTP tunnel associated with the given tunnel-specific + * session token such that future read/write requests to that tunnel will + * be rejected. Each HTTP tunnel must be associated with a session token + * unique to that tunnel via a call {@link #registerTunnel(java.lang.String, org.apache.guacamole.net.GuacamoleTunnel)}. + * + * @param tunnelSessionToken + * The tunnel-specific session token associated with the HTTP tunnel + * being deregistered. + */ + protected void deregisterTunnel(String tunnelSessionToken) { + GuacamoleTunnel tunnel = tunnels.remove(tunnelSessionToken); + if (tunnel != null) + logger.debug("Deregistered tunnel \"{}\".", tunnel.getUUID()); + } + + /** + * Returns the tunnel associated with the given tunnel-specific session + * token, if it has been registered with {@link #registerTunnel(java.lang.String, org.apache.guacamole.net.GuacamoleTunnel)} + * and not yet deregistered with {@link #deregisterTunnel(java.lang.String)}. * - * @param tunnelUUID - * The UUID of registered tunnel. + * @param tunnelSessionToken + * The tunnel-specific session token associated with the HTTP tunnel to + * be retrieved. * * @return - * The tunnel corresponding to the given UUID. + * The tunnel corresponding to the given session token. * * @throws GuacamoleException * If the requested tunnel does not exist because it has not yet been * registered or it has been deregistered. */ - protected GuacamoleTunnel getTunnel(String tunnelUUID) + protected GuacamoleTunnel getTunnel(String tunnelSessionToken) throws GuacamoleException { // Pull tunnel from map - GuacamoleTunnel tunnel = tunnels.get(tunnelUUID); + GuacamoleTunnel tunnel = tunnels.get(tunnelSessionToken); if (tunnel == null) throw new GuacamoleResourceNotFoundException("No such tunnel."); @@ -209,52 +275,54 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { if (query == null) throw new GuacamoleClientException("No query string provided."); - // If connect operation, call doConnect() and return tunnel UUID - // in response. + // If connect operation, call doConnect() and return tunnel + // session token and UUID in response if (query.equals("connect")) { GuacamoleTunnel tunnel = doConnect(request); - if (tunnel != null) { + if (tunnel == null) + throw new GuacamoleResourceNotFoundException("No tunnel created."); - // Register newly-created tunnel - registerTunnel(tunnel); + // Register newly-created tunnel + String tunnelSessionToken = generateToken(); + registerTunnel(tunnelSessionToken, tunnel); - try { - // Ensure buggy browsers do not cache response - response.setHeader("Cache-Control", "no-cache"); - - // Send UUID to client - response.getWriter().print(tunnel.getUUID().toString()); - } - catch (IOException e) { - throw new GuacamoleServerException(e); - } + try { + // Ensure buggy browsers do not cache response + response.setHeader("Cache-Control", "no-cache"); + // Include tunnel session token for future requests + response.setHeader(TUNNEL_TOKEN_HEADER_NAME, tunnelSessionToken); + + // Send UUID to client + response.getWriter().print(tunnel.getUUID().toString()); + } + catch (IOException e) { + throw new GuacamoleServerException(e); } - // Failed to connect - else - throw new GuacamoleResourceNotFoundException("No tunnel created."); + // Connection successful + return; } - // If read operation, call doRead() with tunnel UUID, ignoring any - // characters following the tunnel UUID. - else if (query.startsWith(READ_PREFIX)) - doRead(request, response, query.substring( - READ_PREFIX_LENGTH, - READ_PREFIX_LENGTH + UUID_LENGTH)); + // Pull tunnel-specific session token from request + String tunnelSessionToken = request.getHeader(TUNNEL_TOKEN_HEADER_NAME); + if (tunnelSessionToken == null) + throw new GuacamoleClientException("The HTTP tunnel session " + + "token is required for all requests after " + + "connecting."); - // If write operation, call doWrite() with tunnel UUID, ignoring any - // characters following the tunnel UUID. + // Dispatch valid tunnel read/write operations + if (query.startsWith(READ_PREFIX)) + doRead(request, response, tunnelSessionToken); else if (query.startsWith(WRITE_PREFIX)) - doWrite(request, response, query.substring( - WRITE_PREFIX_LENGTH, - WRITE_PREFIX_LENGTH + UUID_LENGTH)); + doWrite(request, response, tunnelSessionToken); // Otherwise, invalid operation else throw new GuacamoleClientException("Invalid tunnel operation: " + query); + } // Catch any thrown guacamole exception and attempt to pass within the @@ -308,20 +376,20 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { * Any data to be sent to the client in response to the write request * should be written to the response body of this HttpServletResponse. * - * @param tunnelUUID - * The UUID of the tunnel to read from, as specified in the write - * request. This tunnel must have been created by a previous call to - * doConnect(). + * @param tunnelSessionToken + * The tunnel-specific session token of the HTTP tunnel to read from, + * as specified in the read request. This tunnel must have been created + * by a previous call to doConnect(). * * @throws GuacamoleException * If an error occurs while handling the read request. */ protected void doRead(HttpServletRequest request, - HttpServletResponse response, String tunnelUUID) + HttpServletResponse response, String tunnelSessionToken) throws GuacamoleException { // Get tunnel, ensure tunnel exists - GuacamoleTunnel tunnel = getTunnel(tunnelUUID); + GuacamoleTunnel tunnel = getTunnel(tunnelSessionToken); // Ensure tunnel is open if (!tunnel.isOpen()) @@ -371,7 +439,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { // Close tunnel immediately upon EOF if (message == null) { - deregisterTunnel(tunnel); + deregisterTunnel(tunnelSessionToken); tunnel.close(); } @@ -385,7 +453,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { catch (GuacamoleConnectionClosedException e) { // Deregister and close - deregisterTunnel(tunnel); + deregisterTunnel(tunnelSessionToken); tunnel.close(); // End-of-instructions marker @@ -398,7 +466,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { catch (GuacamoleException e) { // Deregister and close - deregisterTunnel(tunnel); + deregisterTunnel(tunnelSessionToken); tunnel.close(); throw e; @@ -416,7 +484,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { logger.debug("Error writing to servlet output stream", e); // Deregister and close - deregisterTunnel(tunnel); + deregisterTunnel(tunnelSessionToken); tunnel.close(); } @@ -439,19 +507,19 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { * @param response * The HttpServletResponse associated with the write request received. * - * @param tunnelUUID - * The UUID of the tunnel to write to, as specified in the write - * request. This tunnel must have been created by a previous call to - * doConnect(). + * @param tunnelSessionToken + * The tunnel-specific session token of the HTTP tunnel to write to, + * as specified in the write request. This tunnel must have been created + * by a previous call to doConnect(). * * @throws GuacamoleException * If an error occurs while handling the write request. */ protected void doWrite(HttpServletRequest request, - HttpServletResponse response, String tunnelUUID) + HttpServletResponse response, String tunnelSessionToken) throws GuacamoleException { - GuacamoleTunnel tunnel = getTunnel(tunnelUUID); + GuacamoleTunnel tunnel = getTunnel(tunnelSessionToken); // We still need to set the content type to avoid the default of // text/html, as such a content type would cause some browsers to @@ -498,7 +566,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { catch (IOException e) { // Deregister and close - deregisterTunnel(tunnel); + deregisterTunnel(tunnelSessionToken); tunnel.close(); throw new GuacamoleServerException("I/O Error sending data to server: " + e.getMessage(), e); diff --git a/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js b/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js index 89d64aeaa..944fd4a9a 100644 --- a/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js +++ b/guacamole/src/main/frontend/src/app/auth/service/authenticationService.js @@ -264,10 +264,10 @@ angular.module('auth').factory('authenticationService', ['$injector', * A promise which succeeds only if the token was successfully revoked. */ service.revokeToken = function revokeToken(token) { - return requestService({ + return service.request({ method: 'DELETE', - url: 'api/tokens/' + token - }); + url: 'api/session' + }, token); }; /** @@ -303,7 +303,7 @@ angular.module('auth').factory('authenticationService', ['$injector', * successful. */ service.logout = function logout() { - + // Clear authentication data var token = service.getCurrentToken(); clearAuthenticationResult(); @@ -409,5 +409,44 @@ angular.module('auth').factory('authenticationService', ['$injector', }; + /** + * Makes an HTTP request leveraging the requestService(), automatically + * including the given authentication token using the "Guacamole-Token" + * header. If no token is provided, the user's current authentication token + * is used instead. If the user is not logged in, the "Guacamole-Token" + * header is simply omitted. The provided configuration object is not + * modified by this function. + * + * @param {Object} object + * A configuration object describing the HTTP request to be made by + * requestService(). As described by requestService(), this object must + * be a configuration object accepted by AngularJS' $http service. + * + * @param {string} [token] + * The authentication token to pass with the "Guacamole-Token" header. + * If omitted, and the user is logged in, the user's current + * authentication token will be used. + * + * @returns {Promise.} + * A promise that will resolve with the data from the HTTP response for + * the underlying requestService() call if successful, or reject with + * an @link{Error} describing the failure. + */ + service.request = function request(object, token) { + + // Attempt to use current token if none is provided + token = token || service.getCurrentToken(); + + // Add "Guacamole-Token" header if an authentication token is available + if (token) { + object = _.merge({ + headers : { 'Guacamole-Token' : token } + }, object); + } + + return requestService(object); + + }; + return service; }]); diff --git a/guacamole/src/main/frontend/src/app/rest/services/activeConnectionService.js b/guacamole/src/main/frontend/src/app/rest/services/activeConnectionService.js index 3960b39d5..327153b3b 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/activeConnectionService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/activeConnectionService.js @@ -47,16 +47,10 @@ angular.module('rest').factory('activeConnectionService', ['$injector', */ service.getActiveConnection = function getActiveConnection(dataSource, id) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve active connection - return requestService({ + return authenticationService.request({ method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections/' + encodeURIComponent(id), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections/' + encodeURIComponent(id) }); }; @@ -79,17 +73,13 @@ angular.module('rest').factory('activeConnectionService', ['$injector', */ service.getActiveConnections = function getActiveConnections(dataSource, permissionTypes) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Add permission filter if specified + var httpParameters = {}; if (permissionTypes) httpParameters.permission = permissionTypes; // Retrieve tunnels - return requestService({ + return authenticationService.request({ method : 'GET', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections', params : httpParameters @@ -111,11 +101,6 @@ angular.module('rest').factory('activeConnectionService', ['$injector', */ service.deleteActiveConnections = function deleteActiveConnections(dataSource, identifiers) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Convert provided array of identifiers to a patch var activeConnectionPatch = []; identifiers.forEach(function addActiveConnectionPatch(identifier) { @@ -126,10 +111,9 @@ angular.module('rest').factory('activeConnectionService', ['$injector', }); // Perform active connection deletion via PATCH - return requestService({ + return authenticationService.request({ method : 'PATCH', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections', - params : httpParameters, data : activeConnectionPatch }); @@ -154,18 +138,12 @@ angular.module('rest').factory('activeConnectionService', ['$injector', */ service.getSharingCredentials = function getSharingCredentials(dataSource, id, sharingProfile) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Generate sharing credentials - return requestService({ + return authenticationService.request({ method : 'GET', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections/' + encodeURIComponent(id) - + '/sharingCredentials/' + encodeURIComponent(sharingProfile), - params : httpParameters + + '/sharingCredentials/' + encodeURIComponent(sharingProfile) }); }; diff --git a/guacamole/src/main/frontend/src/app/rest/services/connectionGroupService.js b/guacamole/src/main/frontend/src/app/rest/services/connectionGroupService.js index c6d68be29..ecfdee4f1 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/connectionGroupService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/connectionGroupService.js @@ -59,17 +59,13 @@ angular.module('rest').factory('connectionGroupService', ['$injector', // Use the root connection group ID if no ID is passed in connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Add permission filter if specified + var httpParameters = {}; if (permissionTypes) httpParameters.permission = permissionTypes; // Retrieve connection group - return requestService({ + return authenticationService.request({ cache : cacheService.connections, method : 'GET', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID) + '/tree', @@ -96,17 +92,11 @@ angular.module('rest').factory('connectionGroupService', ['$injector', // Use the root connection group ID if no ID is passed in connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve connection group - return requestService({ + return authenticationService.request({ cache : cacheService.connections, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID) }); }; @@ -126,17 +116,11 @@ angular.module('rest').factory('connectionGroupService', ['$injector', */ service.saveConnectionGroup = function saveConnectionGroup(dataSource, connectionGroup) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // If connection group is new, add it and set the identifier automatically if (!connectionGroup.identifier) { - return requestService({ + return authenticationService.request({ method : 'POST', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups', - params : httpParameters, data : connectionGroup }) @@ -153,10 +137,9 @@ angular.module('rest').factory('connectionGroupService', ['$injector', // Otherwise, update the existing connection group else { - return requestService({ + return authenticationService.request({ method : 'PUT', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier), - params : httpParameters, data : connectionGroup }) @@ -184,16 +167,10 @@ angular.module('rest').factory('connectionGroupService', ['$injector', */ service.deleteConnectionGroup = function deleteConnectionGroup(dataSource, connectionGroup) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Delete connection group - return requestService({ + return authenticationService.request({ method : 'DELETE', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier) }) // Clear the cache diff --git a/guacamole/src/main/frontend/src/app/rest/services/connectionService.js b/guacamole/src/main/frontend/src/app/rest/services/connectionService.js index 8b9360d00..3531177ea 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/connectionService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/connectionService.js @@ -47,17 +47,11 @@ angular.module('rest').factory('connectionService', ['$injector', */ service.getConnection = function getConnection(dataSource, id) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve connection - return requestService({ + return authenticationService.request({ cache : cacheService.connections, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) }); }; @@ -76,16 +70,10 @@ angular.module('rest').factory('connectionService', ['$injector', */ service.getConnectionHistory = function getConnectionHistory(dataSource, id) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve connection history - return requestService({ + return authenticationService.request({ method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/history', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/history' }); }; @@ -104,17 +92,11 @@ angular.module('rest').factory('connectionService', ['$injector', */ service.getConnectionParameters = function getConnectionParameters(dataSource, id) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve connection parameters - return requestService({ + return authenticationService.request({ cache : cacheService.connections, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/parameters', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/parameters' }); }; @@ -134,17 +116,11 @@ angular.module('rest').factory('connectionService', ['$injector', */ service.saveConnection = function saveConnection(dataSource, connection) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // If connection is new, add it and set the identifier automatically if (!connection.identifier) { - return requestService({ + return authenticationService.request({ method : 'POST', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections', - params : httpParameters, data : connection }) @@ -161,10 +137,9 @@ angular.module('rest').factory('connectionService', ['$injector', // Otherwise, update the existing connection else { - return requestService({ + return authenticationService.request({ method : 'PUT', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier), - params : httpParameters, data : connection }) @@ -192,16 +167,10 @@ angular.module('rest').factory('connectionService', ['$injector', */ service.deleteConnection = function deleteConnection(dataSource, connection) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Delete connection - return requestService({ + return authenticationService.request({ method : 'DELETE', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier) }) // Clear the cache diff --git a/guacamole/src/main/frontend/src/app/rest/services/historyService.js b/guacamole/src/main/frontend/src/app/rest/services/historyService.js index f4e50272a..96291e76a 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/historyService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/historyService.js @@ -60,10 +60,7 @@ angular.module('rest').factory('historyService', ['$injector', service.getConnectionHistory = function getConnectionHistory(dataSource, requiredContents, sortPredicates) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; + var httpParameters = {}; // Filter according to contents if restrictions are specified if (requiredContents) @@ -74,7 +71,7 @@ angular.module('rest').factory('historyService', ['$injector', httpParameters.order = sortPredicates; // Retrieve connection history - return requestService({ + return authenticationService.request({ method : 'GET', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/history/connections', params : httpParameters diff --git a/guacamole/src/main/frontend/src/app/rest/services/languageService.js b/guacamole/src/main/frontend/src/app/rest/services/languageService.js index 5b1ecde89..8f55e2f11 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/languageService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/languageService.js @@ -41,17 +41,11 @@ angular.module('rest').factory('languageService', ['$injector', */ service.getLanguages = function getLanguages() { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available languages - return requestService({ + return authenticationService.request({ cache : cacheService.languages, method : 'GET', - url : 'api/languages', - params : httpParameters + url : 'api/languages' }); }; diff --git a/guacamole/src/main/frontend/src/app/rest/services/membershipService.js b/guacamole/src/main/frontend/src/app/rest/services/membershipService.js index 58181c897..60c5c97e3 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/membershipService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/membershipService.js @@ -144,17 +144,11 @@ angular.module('rest').factory('membershipService', ['$injector', */ service.getUserGroups = function getUserGroups(dataSource, identifier, group) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve parent groups - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : getUserGroupsResourceURL(dataSource, identifier, group), - params : httpParameters + url : getUserGroupsResourceURL(dataSource, identifier, group) }); }; @@ -193,16 +187,10 @@ angular.module('rest').factory('membershipService', ['$injector', service.patchUserGroups = function patchUserGroups(dataSource, identifier, addToUserGroups, removeFromUserGroups, group) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Update parent user groups - return requestService({ + return authenticationService.request({ method : 'PATCH', url : getUserGroupsResourceURL(dataSource, identifier, group), - params : httpParameters, data : getRelatedObjectPatch(addToUserGroups, removeFromUserGroups) }) @@ -232,17 +220,11 @@ angular.module('rest').factory('membershipService', ['$injector', */ service.getMemberUsers = function getMemberUsers(dataSource, identifier) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve member users - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers' }); }; @@ -275,16 +257,10 @@ angular.module('rest').factory('membershipService', ['$injector', service.patchMemberUsers = function patchMemberUsers(dataSource, identifier, usersToAdd, usersToRemove) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Update member users - return requestService({ + return authenticationService.request({ method : 'PATCH', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers', - params : httpParameters, data : getRelatedObjectPatch(usersToAdd, usersToRemove) }) @@ -316,17 +292,11 @@ angular.module('rest').factory('membershipService', ['$injector', */ service.getMemberUserGroups = function getMemberUserGroups(dataSource, identifier) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve member user groups - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups' }); }; @@ -360,16 +330,10 @@ angular.module('rest').factory('membershipService', ['$injector', service.patchMemberUserGroups = function patchMemberUserGroups(dataSource, identifier, userGroupsToAdd, userGroupsToRemove) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Update member user groups - return requestService({ + return authenticationService.request({ method : 'PATCH', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups', - params : httpParameters, data : getRelatedObjectPatch(userGroupsToAdd, userGroupsToRemove) }) diff --git a/guacamole/src/main/frontend/src/app/rest/services/patchService.js b/guacamole/src/main/frontend/src/app/rest/services/patchService.js index de9f6bcab..991a54a52 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/patchService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/patchService.js @@ -42,17 +42,11 @@ angular.module('rest').factory('patchService', ['$injector', */ service.getPatches = function getPatches() { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve all applicable HTML patches - return requestService({ + return authenticationService.request({ cache : cacheService.patches, method : 'GET', - url : 'api/patches', - params : httpParameters + url : 'api/patches' }); }; diff --git a/guacamole/src/main/frontend/src/app/rest/services/permissionService.js b/guacamole/src/main/frontend/src/app/rest/services/permissionService.js index 21c5a02ec..ac09fa1df 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/permissionService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/permissionService.js @@ -105,17 +105,11 @@ angular.module('rest').factory('permissionService', ['$injector', */ service.getEffectivePermissions = function getEffectivePermissions(dataSource, userID) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve user permissions - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : getEffectivePermissionsResourceURL(dataSource, userID), - params : httpParameters + url : getEffectivePermissionsResourceURL(dataSource, userID) }); }; @@ -198,17 +192,11 @@ angular.module('rest').factory('permissionService', ['$injector', */ service.getPermissions = function getPermissions(dataSource, identifier, group) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve user/group permissions - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : getPermissionsResourceURL(dataSource, identifier, group), - params : httpParameters + url : getPermissionsResourceURL(dataSource, identifier, group) }); }; @@ -333,11 +321,6 @@ angular.module('rest').factory('permissionService', ['$injector', var permissionPatch = []; - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Add all the add operations to the patch addPatchOperations(permissionPatch, PermissionPatch.Operation.ADD, permissionsToAdd); @@ -345,10 +328,9 @@ angular.module('rest').factory('permissionService', ['$injector', addPatchOperations(permissionPatch, PermissionPatch.Operation.REMOVE, permissionsToRemove); // Patch user/group permissions - return requestService({ + return authenticationService.request({ method : 'PATCH', url : getPermissionsResourceURL(dataSource, identifier, group), - params : httpParameters, data : permissionPatch }) diff --git a/guacamole/src/main/frontend/src/app/rest/services/schemaService.js b/guacamole/src/main/frontend/src/app/rest/services/schemaService.js index 61c8639de..dee10e1bd 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/schemaService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/schemaService.js @@ -49,17 +49,11 @@ angular.module('rest').factory('schemaService', ['$injector', */ service.getUserAttributes = function getUserAttributes(dataSource) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available user attributes - return requestService({ + return authenticationService.request({ cache : cacheService.schema, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userAttributes', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userAttributes' }); }; @@ -83,17 +77,11 @@ angular.module('rest').factory('schemaService', ['$injector', */ service.getUserGroupAttributes = function getUserGroupAttributes(dataSource) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available user group attributes - return requestService({ + return authenticationService.request({ cache : cacheService.schema, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userGroupAttributes', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userGroupAttributes' }); }; @@ -117,17 +105,11 @@ angular.module('rest').factory('schemaService', ['$injector', */ service.getConnectionAttributes = function getConnectionAttributes(dataSource) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available connection attributes - return requestService({ + return authenticationService.request({ cache : cacheService.schema, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionAttributes', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionAttributes' }); }; @@ -151,17 +133,11 @@ angular.module('rest').factory('schemaService', ['$injector', */ service.getSharingProfileAttributes = function getSharingProfileAttributes(dataSource) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available sharing profile attributes - return requestService({ + return authenticationService.request({ cache : cacheService.schema, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/sharingProfileAttributes', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/sharingProfileAttributes' }); }; @@ -185,17 +161,11 @@ angular.module('rest').factory('schemaService', ['$injector', */ service.getConnectionGroupAttributes = function getConnectionGroupAttributes(dataSource) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available connection group attributes - return requestService({ + return authenticationService.request({ cache : cacheService.schema, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionGroupAttributes', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionGroupAttributes' }); }; @@ -216,17 +186,11 @@ angular.module('rest').factory('schemaService', ['$injector', */ service.getProtocols = function getProtocols(dataSource) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve available protocols - return requestService({ + return authenticationService.request({ cache : cacheService.schema, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/protocols', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/protocols' }); }; diff --git a/guacamole/src/main/frontend/src/app/rest/services/sharingProfileService.js b/guacamole/src/main/frontend/src/app/rest/services/sharingProfileService.js index 0d8b66d2e..57055ec76 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/sharingProfileService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/sharingProfileService.js @@ -49,17 +49,11 @@ angular.module('rest').factory('sharingProfileService', ['$injector', */ service.getSharingProfile = function getSharingProfile(dataSource, id) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve sharing profile - return requestService({ + return authenticationService.request({ cache : cacheService.connections, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id) }); }; @@ -78,17 +72,11 @@ angular.module('rest').factory('sharingProfileService', ['$injector', */ service.getSharingProfileParameters = function getSharingProfileParameters(dataSource, id) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve sharing profile parameters - return requestService({ + return authenticationService.request({ cache : cacheService.connections, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id) + '/parameters', - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id) + '/parameters' }); }; @@ -109,17 +97,11 @@ angular.module('rest').factory('sharingProfileService', ['$injector', */ service.saveSharingProfile = function saveSharingProfile(dataSource, sharingProfile) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // If sharing profile is new, add it and set the identifier automatically if (!sharingProfile.identifier) { - return requestService({ + return authenticationService.request({ method : 'POST', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles', - params : httpParameters, data : sharingProfile }) @@ -136,10 +118,9 @@ angular.module('rest').factory('sharingProfileService', ['$injector', // Otherwise, update the existing sharing profile else { - return requestService({ + return authenticationService.request({ method : 'PUT', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier), - params : httpParameters, data : sharingProfile }) @@ -168,16 +149,10 @@ angular.module('rest').factory('sharingProfileService', ['$injector', */ service.deleteSharingProfile = function deleteSharingProfile(dataSource, sharingProfile) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Delete sharing profile - return requestService({ + return authenticationService.request({ method : 'DELETE', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier) }) // Clear the cache diff --git a/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js b/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js index 3cfdb35e1..6a637ff71 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/tunnelService.js @@ -65,16 +65,10 @@ angular.module('rest').factory('tunnelService', ['$injector', */ service.getTunnels = function getTunnels() { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve tunnels - return requestService({ + return authenticationService.request({ method : 'GET', - url : 'api/session/tunnels', - params : httpParameters + url : 'api/session/tunnels' }); }; @@ -124,17 +118,11 @@ angular.module('rest').factory('tunnelService', ['$injector', */ service.getSharingProfiles = function getSharingProfiles(tunnel) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve all associated sharing profiles - return requestService({ + return authenticationService.request({ method : 'GET', url : 'api/session/tunnels/' + encodeURIComponent(tunnel) - + '/activeConnection/connection/sharingProfiles', - params : httpParameters + + '/activeConnection/connection/sharingProfiles' }); }; @@ -160,18 +148,12 @@ angular.module('rest').factory('tunnelService', ['$injector', */ service.getSharingCredentials = function getSharingCredentials(tunnel, sharingProfile) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Generate sharing credentials - return requestService({ + return authenticationService.request({ method : 'GET', url : 'api/session/tunnels/' + encodeURIComponent(tunnel) + '/activeConnection/sharingCredentials/' - + encodeURIComponent(sharingProfile), - params : httpParameters + + encodeURIComponent(sharingProfile) }); }; diff --git a/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js b/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js index ad29837f5..090efa944 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/userGroupService.js @@ -52,17 +52,13 @@ angular.module('rest').factory('userGroupService', ['$injector', */ service.getUserGroups = function getUserGroups(dataSource, permissionTypes) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Add permission filter if specified + var httpParameters = {}; if (permissionTypes) httpParameters.permission = permissionTypes; // Retrieve user groups - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups', @@ -89,17 +85,11 @@ angular.module('rest').factory('userGroupService', ['$injector', */ service.getUserGroup = function getUserGroup(dataSource, identifier) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve user group - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) }); }; @@ -122,16 +112,10 @@ angular.module('rest').factory('userGroupService', ['$injector', */ service.deleteUserGroup = function deleteUserGroup(dataSource, userGroup) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Delete user group - return requestService({ + return authenticationService.request({ method : 'DELETE', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier) }) // Clear the cache @@ -160,16 +144,10 @@ angular.module('rest').factory('userGroupService', ['$injector', */ service.createUserGroup = function createUserGroup(dataSource, userGroup) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Create user group - return requestService({ + return authenticationService.request({ method : 'POST', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups', - params : httpParameters, data : userGroup }) @@ -198,16 +176,10 @@ angular.module('rest').factory('userGroupService', ['$injector', */ service.saveUserGroup = function saveUserGroup(dataSource, userGroup) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Update user group - return requestService({ + return authenticationService.request({ method : 'PUT', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier), - params : httpParameters, data : userGroup }) diff --git a/guacamole/src/main/frontend/src/app/rest/services/userService.js b/guacamole/src/main/frontend/src/app/rest/services/userService.js index b04420521..faa26b40a 100644 --- a/guacamole/src/main/frontend/src/app/rest/services/userService.js +++ b/guacamole/src/main/frontend/src/app/rest/services/userService.js @@ -56,17 +56,13 @@ angular.module('rest').factory('userService', ['$injector', */ service.getUsers = function getUsers(dataSource, permissionTypes) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Add permission filter if specified + var httpParameters = {}; if (permissionTypes) httpParameters.permission = permissionTypes; // Retrieve users - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users', @@ -93,17 +89,11 @@ angular.module('rest').factory('userService', ['$injector', */ service.getUser = function getUser(dataSource, username) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Retrieve user - return requestService({ + return authenticationService.request({ cache : cacheService.users, method : 'GET', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username) }); }; @@ -126,16 +116,10 @@ angular.module('rest').factory('userService', ['$injector', */ service.deleteUser = function deleteUser(dataSource, user) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Delete user - return requestService({ + return authenticationService.request({ method : 'DELETE', - url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username), - params : httpParameters + url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username) }) // Clear the cache @@ -164,16 +148,10 @@ angular.module('rest').factory('userService', ['$injector', */ service.createUser = function createUser(dataSource, user) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Create user - return requestService({ + return authenticationService.request({ method : 'POST', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users', - params : httpParameters, data : user }) @@ -202,16 +180,10 @@ angular.module('rest').factory('userService', ['$injector', */ service.saveUser = function saveUser(dataSource, user) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Update user - return requestService({ + return authenticationService.request({ method : 'PUT', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username), - params : httpParameters, data : user }) @@ -247,16 +219,10 @@ angular.module('rest').factory('userService', ['$injector', service.updateUserPassword = function updateUserPassword(dataSource, username, oldPassword, newPassword) { - // Build HTTP parameters set - var httpParameters = { - token : authenticationService.getCurrentToken() - }; - // Update user password - return requestService({ + return authenticationService.request({ method : 'PUT', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username) + '/password', - params : httpParameters, data : new UserPasswordUpdate({ oldPassword : oldPassword, newPassword : newPassword diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java index 85430ae19..ecb99a473 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java @@ -21,7 +21,6 @@ package org.apache.guacamole.rest; import javax.inject.Inject; import javax.inject.Singleton; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -32,6 +31,7 @@ import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.rest.auth.AuthenticationService; +import org.glassfish.jersey.server.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,37 +50,20 @@ public class RESTExceptionMapper implements ExceptionMapper { private final Logger logger = LoggerFactory.getLogger(RESTExceptionMapper.class); /** - * The HttpServletRequest for the Throwable being intercepted. Despite this - * class being a Singleton, this object will always be scoped with the - * current request for the Throwable that is being processed by this class. + * The ContainerRequest representing the HTTP request that resulted in the + * Throwable being intercepted. Despite this class being a Singleton, this + * object will always be scoped with the current request for the Throwable + * that is being processed by this class. */ @Context - private HttpServletRequest request; + private ContainerRequest request; /** * The authentication service associated with the currently active session. */ @Inject private AuthenticationService authenticationService; - - /** - * Returns the authentication token that is in use in the current session, - * if present, or null if otherwise. - * - * @return - * The authentication token for the current session, or null if no - * token is present. - */ - private String getAuthenticationToken() { - String token = request.getParameter("token"); - if (token != null && !token.isEmpty()) - return token; - - return null; - - } - @Override public Response toResponse(Throwable t) { @@ -90,8 +73,7 @@ public class RESTExceptionMapper implements ExceptionMapper { // Ensure any associated session is invalidated if unauthorized if (t instanceof GuacamoleUnauthorizedException) { - String token = getAuthenticationToken(); - + String token = authenticationService.getAuthenticationToken(request); if (authenticationService.destroyGuacamoleSession(token)) logger.debug("Implicitly invalidated session for token \"{}\"", token); } @@ -135,4 +117,4 @@ public class RESTExceptionMapper implements ExceptionMapper { } -} \ No newline at end of file +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/TokenParam.java b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParam.java new file mode 100644 index 000000000..53c972254 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParam.java @@ -0,0 +1,34 @@ +/* + * 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.rest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation which automatically maps the authentication token used by + * Guacamole's REST API, regardless of whether that token is received via an + * HTTP header or via a query parameter. + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TokenParam {} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/TokenParamProvider.java b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParamProvider.java new file mode 100644 index 000000000..b0da96188 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParamProvider.java @@ -0,0 +1,60 @@ +/* + * 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.rest; + +import java.util.function.Function; +import javax.inject.Inject; +import javax.ws.rs.ext.Provider; +import org.apache.guacamole.rest.auth.AuthenticationService; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.model.Parameter; +import org.glassfish.jersey.server.spi.internal.ValueParamProvider; + +/** + * Provider which automatically maps Guacamole authentication tokens received + * via REST API requests to parameters that have been annotated with the + * @TokenParam annotation. + */ +@Provider +public class TokenParamProvider implements ValueParamProvider { + + /** + * Service for authenticating users and working with the resulting + * authentication tokens. + */ + @Inject + private AuthenticationService authenticationService; + + @Override + public Function getValueProvider(Parameter parameter) { + + if (parameter.getAnnotation(TokenParam.class) == null) + return null; + + return (request) -> authenticationService.getAuthenticationToken(request); + + } + + @Override + public PriorityType getPriority() { + return Priority.HIGH; + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index a663ab783..ce8a9fb0c 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -41,6 +41,7 @@ import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsExce import org.apache.guacamole.net.event.AuthenticationFailureEvent; import org.apache.guacamole.net.event.AuthenticationSuccessEvent; import org.apache.guacamole.rest.event.ListenerService; +import org.glassfish.jersey.server.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,6 +92,18 @@ public class AuthenticationService { @Inject private ListenerService listenerService; + /** + * The name of the HTTP header that may contain the authentication token + * used by the Guacamole REST API. + */ + public static final String TOKEN_HEADER_NAME = "Guacamole-Token"; + + /** + * The name of the query parameter that may contain the authentication + * token used by the Guacamole REST API. + */ + public static final String TOKEN_PARAMETER_NAME = "token"; + /** * Regular expression which matches any IPv4 address. */ @@ -541,4 +554,34 @@ public class AuthenticationService { return getGuacamoleSession(authToken).getUserContexts(); } + /** + * Returns the authentication token sent within the given request, if + * present, or null if otherwise. Authentication tokens may be sent via + * the "Guacamole-Token" header or the "token" query parameter. If both + * the header and a parameter are used, the header is given priority. + * + * @param request + * The HTTP request to retrieve the authentication token from. + * + * @return + * The authentication token within the given request, or null if no + * token is present. + */ + public String getAuthenticationToken(ContainerRequest request) { + + // Give priority to token within HTTP header + String token = request.getHeaderString(TOKEN_HEADER_NAME); + if (token != null && !token.isEmpty()) + return token; + + // If no token was provided via HTTP headers, fall back to using + // query parameters + token = request.getUriInfo().getQueryParameters().getFirst(TOKEN_PARAMETER_NAME); + if (token != null && !token.isEmpty()) + return token; + + return null; + + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java index 546026b53..90974d1ec 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java @@ -23,10 +23,10 @@ import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSession; +import org.apache.guacamole.rest.TokenParam; import org.apache.guacamole.rest.auth.AuthenticationService; /** @@ -67,12 +67,12 @@ public class SessionRESTService { * If the authentication token is invalid. */ @Path("/") - public SessionResource getSessionResource(@QueryParam("token") String authToken) + public SessionResource getSessionResource(@TokenParam String authToken) throws GuacamoleException { // Return a resource exposing the retrieved session GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); - return sessionResourceFactory.create(session); + return sessionResourceFactory.create(authToken, session); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java index 702ec867d..0c334b068 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResource.java @@ -23,6 +23,7 @@ import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import javax.inject.Inject; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -31,6 +32,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.rest.auth.AuthenticationService; import org.apache.guacamole.rest.tunnel.TunnelCollectionResource; import org.apache.guacamole.rest.tunnel.TunnelCollectionResourceFactory; @@ -47,6 +49,18 @@ public class SessionResource { */ private final GuacamoleSession session; + /** + * The authentication token associated with the GuacamoleSession being + * exposed by this SessionResource. + */ + private final String token; + + /** + * Service for authenticating users and managing their Guacamole sessions. + */ + @Inject + private AuthenticationService authenticationService; + /** * Factory for creating UserContextResources which expose a given * UserContext. @@ -65,12 +79,16 @@ public class SessionResource { * Creates a new SessionResource which exposes the data within the given * GuacamoleSession. * + * @param token + * The authentication token associated with the given session. + * * @param session * The GuacamoleSession which should be exposed through this * SessionResource. */ @AssistedInject - public SessionResource(@Assisted GuacamoleSession session) { + public SessionResource(@Assisted String token, @Assisted GuacamoleSession session) { + this.token = token; this.session = session; } @@ -149,4 +167,21 @@ public class SessionResource { return tunnelCollectionResourceFactory.create(session); } + /** + * Invalidates the GuacamoleSession exposed by this SessionResource, + * including the associated authentication token. + * + * @throws GuacamoleException + * If the authentication token originally provided when this + * SessionResource was created no longer exists. + */ + @DELETE + public void invalidate() throws GuacamoleException { + + // Invalidate session, if it exists + if (!authenticationService.destroyGuacamoleSession(token)) + throw new GuacamoleResourceNotFoundException("No such token."); + + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResourceFactory.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResourceFactory.java index a166b37b0..462eebe1e 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResourceFactory.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionResourceFactory.java @@ -31,6 +31,9 @@ public interface SessionResourceFactory { * Creates a new SessionResource which exposes the contents of the * given GuacamoleSession. * + * @param token + * The authentication token associated with the given session. + * * @param session * The GuacamoleSession whose contents should be exposed. * @@ -38,6 +41,6 @@ public interface SessionResourceFactory { * A new SessionResource which exposes the contents of the given * GuacamoleSession. */ - SessionResource create(GuacamoleSession session); + SessionResource create(String token, GuacamoleSession session); }