Merge 1.4.0 changes back to master.

This commit is contained in:
Virtually Nick
2021-10-25 07:35:52 -04:00
24 changed files with 508 additions and 499 deletions

View File

@@ -309,6 +309,25 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
*/ */
var extraHeaders = 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. * Adds the configured additional headers to the given request.
* *
@@ -453,6 +472,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
message_xmlhttprequest.withCredentials = withCredentials; message_xmlhttprequest.withCredentials = withCredentials;
addExtraHeaders(message_xmlhttprequest, extraHeaders); addExtraHeaders(message_xmlhttprequest, extraHeaders);
message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream"); message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream");
message_xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken);
// Once response received, send next queued event. // Once response received, send next queued event.
message_xmlhttprequest.onreadystatechange = function() { message_xmlhttprequest.onreadystatechange = function() {
@@ -697,6 +717,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
// Make request, increment request ID // Make request, increment request ID
var xmlhttprequest = new XMLHttpRequest(); var xmlhttprequest = new XMLHttpRequest();
xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + (request_id++)); xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + (request_id++));
xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken);
xmlhttprequest.withCredentials = withCredentials; xmlhttprequest.withCredentials = withCredentials;
addExtraHeaders(xmlhttprequest, extraHeaders); addExtraHeaders(xmlhttprequest, extraHeaders);
xmlhttprequest.send(null); xmlhttprequest.send(null);
@@ -728,8 +749,15 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
reset_timeout(); reset_timeout();
// Get UUID from response // Get UUID and HTTP-specific tunnel session token from response
tunnel.setUUID(connect_xmlhttprequest.responseText); 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 // Mark as open
tunnel.setState(Guacamole.Tunnel.State.OPEN); tunnel.setState(Guacamole.Tunnel.State.OPEN);

View File

@@ -58,7 +58,8 @@ class GuacamoleHTTPTunnelMap {
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 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<String, GuacamoleHTTPTunnel> tunnelMap = private final ConcurrentMap<String, GuacamoleHTTPTunnel> tunnelMap =
new ConcurrentHashMap<String, GuacamoleHTTPTunnel>(); new ConcurrentHashMap<String, GuacamoleHTTPTunnel>();
@@ -141,22 +142,22 @@ class GuacamoleHTTPTunnelMap {
} }
/** /**
* Returns the GuacamoleTunnel having the given UUID, wrapped within a * Returns the GuacamoleTunnel associated with the given tunnel-specific
* GuacamoleHTTPTunnel. If the no tunnel having the given UUID is * session token, wrapped within a GuacamoleHTTPTunnel. If the no tunnel
* available, null is returned. * is associated with the given token, null is returned.
* *
* @param uuid * @param tunnelSessionToken
* The UUID of the tunnel to retrieve. * The tunnel-specific session token of the HTTP tunnel to retrieve.
* *
* @return * @return
* The GuacamoleTunnel having the given UUID, wrapped within a * The GuacamoleTunnel associated with the given tunnel-specific
* GuacamoleHTTPTunnel, if such a tunnel exists, or null if there is no * session token, wrapped within a GuacamoleHTTPTunnel, if such a
* such tunnel. * 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 // Update the last access time
GuacamoleHTTPTunnel tunnel = tunnelMap.get(uuid); GuacamoleHTTPTunnel tunnel = tunnelMap.get(tunnelSessionToken);
if (tunnel != null) if (tunnel != null)
tunnel.access(); tunnel.access();
@@ -169,32 +170,34 @@ class GuacamoleHTTPTunnelMap {
* Registers that a new connection has been established using HTTP via the * Registers that a new connection has been established using HTTP via the
* given GuacamoleTunnel. * given GuacamoleTunnel.
* *
* @param uuid * @param tunnelSessionToken
* The UUID of the tunnel being added (registered). * The tunnel-specific session token of the HTTP tunnel being added
* (registered).
* *
* @param tunnel * @param tunnel
* The GuacamoleTunnel being registered, its associated connection * The GuacamoleTunnel being registered, its associated connection
* having just been established via HTTP. * having just been established via HTTP.
*/ */
public void put(String uuid, GuacamoleTunnel tunnel) { public void put(String tunnelSessionToken, GuacamoleTunnel tunnel) {
tunnelMap.put(uuid, new GuacamoleHTTPTunnel(tunnel)); tunnelMap.put(tunnelSessionToken, new GuacamoleHTTPTunnel(tunnel));
} }
/** /**
* Removes the GuacamoleTunnel having the given UUID, if such a tunnel * Removes the GuacamoleTunnel associated with the given tunnel-specific
* exists. The original tunnel is returned wrapped within a * session token, if such a tunnel exists. The original tunnel is returned
* GuacamoleHTTPTunnel. * wrapped within a GuacamoleHTTPTunnel.
* *
* @param uuid * @param tunnelSessionToken
* The UUID of the tunnel to remove (deregister). * The tunnel-specific session token of the HTTP tunnel to remove
* (deregister).
* *
* @return * @return
* The GuacamoleTunnel having the given UUID, if such a tunnel exists, * The GuacamoleTunnel having the given tunnel-specific session token,
* wrapped within a GuacamoleHTTPTunnel, or null if no such tunnel * if such a tunnel exists, wrapped within a GuacamoleHTTPTunnel, or
* exists and no removal was performed. * null if no such tunnel exists and no removal was performed.
*/ */
public GuacamoleHTTPTunnel remove(String uuid) { public GuacamoleHTTPTunnel remove(String tunnelSessionToken) {
return tunnelMap.remove(uuid); return tunnelMap.remove(tunnelSessionToken);
} }
/** /**

View File

@@ -25,6 +25,8 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.security.SecureRandom;
import java.util.Base64;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; 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.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter; import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -53,10 +54,17 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
private final Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelServlet.class); 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(); 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. * 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:"; 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 * Registers the given tunnel such that future read/write requests to that
* tunnel will be properly directed. * 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 * @param tunnel
* The tunnel to register. * The tunnel to register.
*/ */
@Deprecated
protected void registerTunnel(GuacamoleTunnel tunnel) { 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()); 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 * Deregisters the given tunnel such that future read/write requests to
* that tunnel will be rejected. * 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 * @param tunnel
* The tunnel to deregister. * The tunnel to deregister.
*/ */
@Deprecated
protected void deregisterTunnel(GuacamoleTunnel tunnel) { protected void deregisterTunnel(GuacamoleTunnel tunnel) {
tunnels.remove(tunnel.getUUID().toString()); deregisterTunnel(tunnel.getUUID().toString());
}
/**
* 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()); logger.debug("Deregistered tunnel \"{}\".", tunnel.getUUID());
} }
/** /**
* Returns the tunnel with the given UUID, if it has been registered with * Returns the tunnel associated with the given tunnel-specific session
* registerTunnel() and not yet deregistered with deregisterTunnel(). * 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 * @param tunnelSessionToken
* The UUID of registered tunnel. * The tunnel-specific session token associated with the HTTP tunnel to
* be retrieved.
* *
* @return * @return
* The tunnel corresponding to the given UUID. * The tunnel corresponding to the given session token.
* *
* @throws GuacamoleException * @throws GuacamoleException
* If the requested tunnel does not exist because it has not yet been * If the requested tunnel does not exist because it has not yet been
* registered or it has been deregistered. * registered or it has been deregistered.
*/ */
protected GuacamoleTunnel getTunnel(String tunnelUUID) protected GuacamoleTunnel getTunnel(String tunnelSessionToken)
throws GuacamoleException { throws GuacamoleException {
// Pull tunnel from map // Pull tunnel from map
GuacamoleTunnel tunnel = tunnels.get(tunnelUUID); GuacamoleTunnel tunnel = tunnels.get(tunnelSessionToken);
if (tunnel == null) if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No such tunnel."); throw new GuacamoleResourceNotFoundException("No such tunnel.");
@@ -209,20 +275,25 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
if (query == null) if (query == null)
throw new GuacamoleClientException("No query string provided."); throw new GuacamoleClientException("No query string provided.");
// If connect operation, call doConnect() and return tunnel UUID // If connect operation, call doConnect() and return tunnel
// in response. // session token and UUID in response
if (query.equals("connect")) { if (query.equals("connect")) {
GuacamoleTunnel tunnel = doConnect(request); GuacamoleTunnel tunnel = doConnect(request);
if (tunnel != null) { if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No tunnel created.");
// Register newly-created tunnel // Register newly-created tunnel
registerTunnel(tunnel); String tunnelSessionToken = generateToken();
registerTunnel(tunnelSessionToken, tunnel);
try { try {
// Ensure buggy browsers do not cache response // Ensure buggy browsers do not cache response
response.setHeader("Cache-Control", "no-cache"); response.setHeader("Cache-Control", "no-cache");
// Include tunnel session token for future requests
response.setHeader(TUNNEL_TOKEN_HEADER_NAME, tunnelSessionToken);
// Send UUID to client // Send UUID to client
response.getWriter().print(tunnel.getUUID().toString()); response.getWriter().print(tunnel.getUUID().toString());
} }
@@ -230,31 +301,28 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
throw new GuacamoleServerException(e); throw new GuacamoleServerException(e);
} }
} // Connection successful
return;
// Failed to connect
else
throw new GuacamoleResourceNotFoundException("No tunnel created.");
} }
// If read operation, call doRead() with tunnel UUID, ignoring any // Pull tunnel-specific session token from request
// characters following the tunnel UUID. String tunnelSessionToken = request.getHeader(TUNNEL_TOKEN_HEADER_NAME);
else if (query.startsWith(READ_PREFIX)) if (tunnelSessionToken == null)
doRead(request, response, query.substring( throw new GuacamoleClientException("The HTTP tunnel session "
READ_PREFIX_LENGTH, + "token is required for all requests after "
READ_PREFIX_LENGTH + UUID_LENGTH)); + "connecting.");
// If write operation, call doWrite() with tunnel UUID, ignoring any // Dispatch valid tunnel read/write operations
// characters following the tunnel UUID. if (query.startsWith(READ_PREFIX))
doRead(request, response, tunnelSessionToken);
else if (query.startsWith(WRITE_PREFIX)) else if (query.startsWith(WRITE_PREFIX))
doWrite(request, response, query.substring( doWrite(request, response, tunnelSessionToken);
WRITE_PREFIX_LENGTH,
WRITE_PREFIX_LENGTH + UUID_LENGTH));
// Otherwise, invalid operation // Otherwise, invalid operation
else else
throw new GuacamoleClientException("Invalid tunnel operation: " + query); throw new GuacamoleClientException("Invalid tunnel operation: " + query);
} }
// Catch any thrown guacamole exception and attempt to pass within the // 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 * Any data to be sent to the client in response to the write request
* should be written to the response body of this HttpServletResponse. * should be written to the response body of this HttpServletResponse.
* *
* @param tunnelUUID * @param tunnelSessionToken
* The UUID of the tunnel to read from, as specified in the write * The tunnel-specific session token of the HTTP tunnel to read from,
* request. This tunnel must have been created by a previous call to * as specified in the read request. This tunnel must have been created
* doConnect(). * by a previous call to doConnect().
* *
* @throws GuacamoleException * @throws GuacamoleException
* If an error occurs while handling the read request. * If an error occurs while handling the read request.
*/ */
protected void doRead(HttpServletRequest request, protected void doRead(HttpServletRequest request,
HttpServletResponse response, String tunnelUUID) HttpServletResponse response, String tunnelSessionToken)
throws GuacamoleException { throws GuacamoleException {
// Get tunnel, ensure tunnel exists // Get tunnel, ensure tunnel exists
GuacamoleTunnel tunnel = getTunnel(tunnelUUID); GuacamoleTunnel tunnel = getTunnel(tunnelSessionToken);
// Ensure tunnel is open // Ensure tunnel is open
if (!tunnel.isOpen()) if (!tunnel.isOpen())
@@ -371,7 +439,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// Close tunnel immediately upon EOF // Close tunnel immediately upon EOF
if (message == null) { if (message == null) {
deregisterTunnel(tunnel); deregisterTunnel(tunnelSessionToken);
tunnel.close(); tunnel.close();
} }
@@ -385,7 +453,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
catch (GuacamoleConnectionClosedException e) { catch (GuacamoleConnectionClosedException e) {
// Deregister and close // Deregister and close
deregisterTunnel(tunnel); deregisterTunnel(tunnelSessionToken);
tunnel.close(); tunnel.close();
// End-of-instructions marker // End-of-instructions marker
@@ -398,7 +466,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
catch (GuacamoleException e) { catch (GuacamoleException e) {
// Deregister and close // Deregister and close
deregisterTunnel(tunnel); deregisterTunnel(tunnelSessionToken);
tunnel.close(); tunnel.close();
throw e; throw e;
@@ -416,7 +484,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
logger.debug("Error writing to servlet output stream", e); logger.debug("Error writing to servlet output stream", e);
// Deregister and close // Deregister and close
deregisterTunnel(tunnel); deregisterTunnel(tunnelSessionToken);
tunnel.close(); tunnel.close();
} }
@@ -439,19 +507,19 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
* @param response * @param response
* The HttpServletResponse associated with the write request received. * The HttpServletResponse associated with the write request received.
* *
* @param tunnelUUID * @param tunnelSessionToken
* The UUID of the tunnel to write to, as specified in the write * The tunnel-specific session token of the HTTP tunnel to write to,
* request. This tunnel must have been created by a previous call to * as specified in the write request. This tunnel must have been created
* doConnect(). * by a previous call to doConnect().
* *
* @throws GuacamoleException * @throws GuacamoleException
* If an error occurs while handling the write request. * If an error occurs while handling the write request.
*/ */
protected void doWrite(HttpServletRequest request, protected void doWrite(HttpServletRequest request,
HttpServletResponse response, String tunnelUUID) HttpServletResponse response, String tunnelSessionToken)
throws GuacamoleException { throws GuacamoleException {
GuacamoleTunnel tunnel = getTunnel(tunnelUUID); GuacamoleTunnel tunnel = getTunnel(tunnelSessionToken);
// We still need to set the content type to avoid the default of // 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 // 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) { catch (IOException e) {
// Deregister and close // Deregister and close
deregisterTunnel(tunnel); deregisterTunnel(tunnelSessionToken);
tunnel.close(); tunnel.close();
throw new GuacamoleServerException("I/O Error sending data to server: " + e.getMessage(), e); throw new GuacamoleServerException("I/O Error sending data to server: " + e.getMessage(), e);

View File

@@ -264,10 +264,10 @@ angular.module('auth').factory('authenticationService', ['$injector',
* A promise which succeeds only if the token was successfully revoked. * A promise which succeeds only if the token was successfully revoked.
*/ */
service.revokeToken = function revokeToken(token) { service.revokeToken = function revokeToken(token) {
return requestService({ return service.request({
method: 'DELETE', method: 'DELETE',
url: 'api/tokens/' + token url: 'api/session'
}); }, token);
}; };
/** /**
@@ -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.<Object>}
* 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; return service;
}]); }]);

View File

@@ -47,16 +47,10 @@ angular.module('rest').factory('activeConnectionService', ['$injector',
*/ */
service.getActiveConnection = function getActiveConnection(dataSource, id) { service.getActiveConnection = function getActiveConnection(dataSource, id) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve active connection // Retrieve active connection
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections/' + encodeURIComponent(id), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections/' + encodeURIComponent(id)
params : httpParameters
}); });
}; };
@@ -79,17 +73,13 @@ angular.module('rest').factory('activeConnectionService', ['$injector',
*/ */
service.getActiveConnections = function getActiveConnections(dataSource, permissionTypes) { service.getActiveConnections = function getActiveConnections(dataSource, permissionTypes) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Add permission filter if specified // Add permission filter if specified
var httpParameters = {};
if (permissionTypes) if (permissionTypes)
httpParameters.permission = permissionTypes; httpParameters.permission = permissionTypes;
// Retrieve tunnels // Retrieve tunnels
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections',
params : httpParameters params : httpParameters
@@ -111,11 +101,6 @@ angular.module('rest').factory('activeConnectionService', ['$injector',
*/ */
service.deleteActiveConnections = function deleteActiveConnections(dataSource, identifiers) { service.deleteActiveConnections = function deleteActiveConnections(dataSource, identifiers) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Convert provided array of identifiers to a patch // Convert provided array of identifiers to a patch
var activeConnectionPatch = []; var activeConnectionPatch = [];
identifiers.forEach(function addActiveConnectionPatch(identifier) { identifiers.forEach(function addActiveConnectionPatch(identifier) {
@@ -126,10 +111,9 @@ angular.module('rest').factory('activeConnectionService', ['$injector',
}); });
// Perform active connection deletion via PATCH // Perform active connection deletion via PATCH
return requestService({ return authenticationService.request({
method : 'PATCH', method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/activeConnections',
params : httpParameters,
data : activeConnectionPatch data : activeConnectionPatch
}); });
@@ -154,18 +138,12 @@ angular.module('rest').factory('activeConnectionService', ['$injector',
*/ */
service.getSharingCredentials = function getSharingCredentials(dataSource, id, sharingProfile) { service.getSharingCredentials = function getSharingCredentials(dataSource, id, sharingProfile) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Generate sharing credentials // Generate sharing credentials
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) url : 'api/session/data/' + encodeURIComponent(dataSource)
+ '/activeConnections/' + encodeURIComponent(id) + '/activeConnections/' + encodeURIComponent(id)
+ '/sharingCredentials/' + encodeURIComponent(sharingProfile), + '/sharingCredentials/' + encodeURIComponent(sharingProfile)
params : httpParameters
}); });
}; };

View File

@@ -59,17 +59,13 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
// Use the root connection group ID if no ID is passed in // Use the root connection group ID if no ID is passed in
connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER;
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Add permission filter if specified // Add permission filter if specified
var httpParameters = {};
if (permissionTypes) if (permissionTypes)
httpParameters.permission = permissionTypes; httpParameters.permission = permissionTypes;
// Retrieve connection group // Retrieve connection group
return requestService({ return authenticationService.request({
cache : cacheService.connections, cache : cacheService.connections,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID) + '/tree', 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 // Use the root connection group ID if no ID is passed in
connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER;
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve connection group // Retrieve connection group
return requestService({ return authenticationService.request({
cache : cacheService.connections, cache : cacheService.connections,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroupID)
params : httpParameters
}); });
}; };
@@ -126,17 +116,11 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
*/ */
service.saveConnectionGroup = function saveConnectionGroup(dataSource, connectionGroup) { 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 connection group is new, add it and set the identifier automatically
if (!connectionGroup.identifier) { if (!connectionGroup.identifier) {
return requestService({ return authenticationService.request({
method : 'POST', method : 'POST',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups',
params : httpParameters,
data : connectionGroup data : connectionGroup
}) })
@@ -153,10 +137,9 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
// Otherwise, update the existing connection group // Otherwise, update the existing connection group
else { else {
return requestService({ return authenticationService.request({
method : 'PUT', method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier),
params : httpParameters,
data : connectionGroup data : connectionGroup
}) })
@@ -184,16 +167,10 @@ angular.module('rest').factory('connectionGroupService', ['$injector',
*/ */
service.deleteConnectionGroup = function deleteConnectionGroup(dataSource, connectionGroup) { service.deleteConnectionGroup = function deleteConnectionGroup(dataSource, connectionGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete connection group // Delete connection group
return requestService({ return authenticationService.request({
method : 'DELETE', method : 'DELETE',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connectionGroups/' + encodeURIComponent(connectionGroup.identifier)
params : httpParameters
}) })
// Clear the cache // Clear the cache

View File

@@ -47,17 +47,11 @@ angular.module('rest').factory('connectionService', ['$injector',
*/ */
service.getConnection = function getConnection(dataSource, id) { service.getConnection = function getConnection(dataSource, id) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve connection // Retrieve connection
return requestService({ return authenticationService.request({
cache : cacheService.connections, cache : cacheService.connections,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id)
params : httpParameters
}); });
}; };
@@ -76,16 +70,10 @@ angular.module('rest').factory('connectionService', ['$injector',
*/ */
service.getConnectionHistory = function getConnectionHistory(dataSource, id) { service.getConnectionHistory = function getConnectionHistory(dataSource, id) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve connection history // Retrieve connection history
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/history', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/history'
params : httpParameters
}); });
}; };
@@ -104,17 +92,11 @@ angular.module('rest').factory('connectionService', ['$injector',
*/ */
service.getConnectionParameters = function getConnectionParameters(dataSource, id) { service.getConnectionParameters = function getConnectionParameters(dataSource, id) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve connection parameters // Retrieve connection parameters
return requestService({ return authenticationService.request({
cache : cacheService.connections, cache : cacheService.connections,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/parameters', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(id) + '/parameters'
params : httpParameters
}); });
}; };
@@ -134,17 +116,11 @@ angular.module('rest').factory('connectionService', ['$injector',
*/ */
service.saveConnection = function saveConnection(dataSource, connection) { 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 is new, add it and set the identifier automatically
if (!connection.identifier) { if (!connection.identifier) {
return requestService({ return authenticationService.request({
method : 'POST', method : 'POST',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections',
params : httpParameters,
data : connection data : connection
}) })
@@ -161,10 +137,9 @@ angular.module('rest').factory('connectionService', ['$injector',
// Otherwise, update the existing connection // Otherwise, update the existing connection
else { else {
return requestService({ return authenticationService.request({
method : 'PUT', method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier),
params : httpParameters,
data : connection data : connection
}) })
@@ -192,16 +167,10 @@ angular.module('rest').factory('connectionService', ['$injector',
*/ */
service.deleteConnection = function deleteConnection(dataSource, connection) { service.deleteConnection = function deleteConnection(dataSource, connection) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete connection // Delete connection
return requestService({ return authenticationService.request({
method : 'DELETE', method : 'DELETE',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/connections/' + encodeURIComponent(connection.identifier)
params : httpParameters
}) })
// Clear the cache // Clear the cache

View File

@@ -60,10 +60,7 @@ angular.module('rest').factory('historyService', ['$injector',
service.getConnectionHistory = function getConnectionHistory(dataSource, service.getConnectionHistory = function getConnectionHistory(dataSource,
requiredContents, sortPredicates) { requiredContents, sortPredicates) {
// Build HTTP parameters set var httpParameters = {};
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Filter according to contents if restrictions are specified // Filter according to contents if restrictions are specified
if (requiredContents) if (requiredContents)
@@ -74,7 +71,7 @@ angular.module('rest').factory('historyService', ['$injector',
httpParameters.order = sortPredicates; httpParameters.order = sortPredicates;
// Retrieve connection history // Retrieve connection history
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/history/connections', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/history/connections',
params : httpParameters params : httpParameters

View File

@@ -41,17 +41,11 @@ angular.module('rest').factory('languageService', ['$injector',
*/ */
service.getLanguages = function getLanguages() { service.getLanguages = function getLanguages() {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available languages // Retrieve available languages
return requestService({ return authenticationService.request({
cache : cacheService.languages, cache : cacheService.languages,
method : 'GET', method : 'GET',
url : 'api/languages', url : 'api/languages'
params : httpParameters
}); });
}; };

View File

@@ -144,17 +144,11 @@ angular.module('rest').factory('membershipService', ['$injector',
*/ */
service.getUserGroups = function getUserGroups(dataSource, identifier, group) { service.getUserGroups = function getUserGroups(dataSource, identifier, group) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve parent groups // Retrieve parent groups
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : getUserGroupsResourceURL(dataSource, identifier, group), url : getUserGroupsResourceURL(dataSource, identifier, group)
params : httpParameters
}); });
}; };
@@ -193,16 +187,10 @@ angular.module('rest').factory('membershipService', ['$injector',
service.patchUserGroups = function patchUserGroups(dataSource, identifier, service.patchUserGroups = function patchUserGroups(dataSource, identifier,
addToUserGroups, removeFromUserGroups, group) { addToUserGroups, removeFromUserGroups, group) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update parent user groups // Update parent user groups
return requestService({ return authenticationService.request({
method : 'PATCH', method : 'PATCH',
url : getUserGroupsResourceURL(dataSource, identifier, group), url : getUserGroupsResourceURL(dataSource, identifier, group),
params : httpParameters,
data : getRelatedObjectPatch(addToUserGroups, removeFromUserGroups) data : getRelatedObjectPatch(addToUserGroups, removeFromUserGroups)
}) })
@@ -232,17 +220,11 @@ angular.module('rest').factory('membershipService', ['$injector',
*/ */
service.getMemberUsers = function getMemberUsers(dataSource, identifier) { service.getMemberUsers = function getMemberUsers(dataSource, identifier) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve member users // Retrieve member users
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers'
params : httpParameters
}); });
}; };
@@ -275,16 +257,10 @@ angular.module('rest').factory('membershipService', ['$injector',
service.patchMemberUsers = function patchMemberUsers(dataSource, identifier, service.patchMemberUsers = function patchMemberUsers(dataSource, identifier,
usersToAdd, usersToRemove) { usersToAdd, usersToRemove) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update member users // Update member users
return requestService({ return authenticationService.request({
method : 'PATCH', method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUsers',
params : httpParameters,
data : getRelatedObjectPatch(usersToAdd, usersToRemove) data : getRelatedObjectPatch(usersToAdd, usersToRemove)
}) })
@@ -316,17 +292,11 @@ angular.module('rest').factory('membershipService', ['$injector',
*/ */
service.getMemberUserGroups = function getMemberUserGroups(dataSource, identifier) { service.getMemberUserGroups = function getMemberUserGroups(dataSource, identifier) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve member user groups // Retrieve member user groups
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups'
params : httpParameters
}); });
}; };
@@ -360,16 +330,10 @@ angular.module('rest').factory('membershipService', ['$injector',
service.patchMemberUserGroups = function patchMemberUserGroups(dataSource, service.patchMemberUserGroups = function patchMemberUserGroups(dataSource,
identifier, userGroupsToAdd, userGroupsToRemove) { identifier, userGroupsToAdd, userGroupsToRemove) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update member user groups // Update member user groups
return requestService({ return authenticationService.request({
method : 'PATCH', method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier) + '/memberUserGroups',
params : httpParameters,
data : getRelatedObjectPatch(userGroupsToAdd, userGroupsToRemove) data : getRelatedObjectPatch(userGroupsToAdd, userGroupsToRemove)
}) })

View File

@@ -42,17 +42,11 @@ angular.module('rest').factory('patchService', ['$injector',
*/ */
service.getPatches = function getPatches() { service.getPatches = function getPatches() {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve all applicable HTML patches // Retrieve all applicable HTML patches
return requestService({ return authenticationService.request({
cache : cacheService.patches, cache : cacheService.patches,
method : 'GET', method : 'GET',
url : 'api/patches', url : 'api/patches'
params : httpParameters
}); });
}; };

View File

@@ -105,17 +105,11 @@ angular.module('rest').factory('permissionService', ['$injector',
*/ */
service.getEffectivePermissions = function getEffectivePermissions(dataSource, userID) { service.getEffectivePermissions = function getEffectivePermissions(dataSource, userID) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve user permissions // Retrieve user permissions
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : getEffectivePermissionsResourceURL(dataSource, userID), url : getEffectivePermissionsResourceURL(dataSource, userID)
params : httpParameters
}); });
}; };
@@ -198,17 +192,11 @@ angular.module('rest').factory('permissionService', ['$injector',
*/ */
service.getPermissions = function getPermissions(dataSource, identifier, group) { service.getPermissions = function getPermissions(dataSource, identifier, group) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve user/group permissions // Retrieve user/group permissions
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : getPermissionsResourceURL(dataSource, identifier, group), url : getPermissionsResourceURL(dataSource, identifier, group)
params : httpParameters
}); });
}; };
@@ -333,11 +321,6 @@ angular.module('rest').factory('permissionService', ['$injector',
var permissionPatch = []; var permissionPatch = [];
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Add all the add operations to the patch // Add all the add operations to the patch
addPatchOperations(permissionPatch, PermissionPatch.Operation.ADD, permissionsToAdd); addPatchOperations(permissionPatch, PermissionPatch.Operation.ADD, permissionsToAdd);
@@ -345,10 +328,9 @@ angular.module('rest').factory('permissionService', ['$injector',
addPatchOperations(permissionPatch, PermissionPatch.Operation.REMOVE, permissionsToRemove); addPatchOperations(permissionPatch, PermissionPatch.Operation.REMOVE, permissionsToRemove);
// Patch user/group permissions // Patch user/group permissions
return requestService({ return authenticationService.request({
method : 'PATCH', method : 'PATCH',
url : getPermissionsResourceURL(dataSource, identifier, group), url : getPermissionsResourceURL(dataSource, identifier, group),
params : httpParameters,
data : permissionPatch data : permissionPatch
}) })

View File

@@ -49,17 +49,11 @@ angular.module('rest').factory('schemaService', ['$injector',
*/ */
service.getUserAttributes = function getUserAttributes(dataSource) { service.getUserAttributes = function getUserAttributes(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available user attributes // Retrieve available user attributes
return requestService({ return authenticationService.request({
cache : cacheService.schema, cache : cacheService.schema,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userAttributes', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userAttributes'
params : httpParameters
}); });
}; };
@@ -83,17 +77,11 @@ angular.module('rest').factory('schemaService', ['$injector',
*/ */
service.getUserGroupAttributes = function getUserGroupAttributes(dataSource) { service.getUserGroupAttributes = function getUserGroupAttributes(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available user group attributes // Retrieve available user group attributes
return requestService({ return authenticationService.request({
cache : cacheService.schema, cache : cacheService.schema,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userGroupAttributes', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/userGroupAttributes'
params : httpParameters
}); });
}; };
@@ -117,17 +105,11 @@ angular.module('rest').factory('schemaService', ['$injector',
*/ */
service.getConnectionAttributes = function getConnectionAttributes(dataSource) { service.getConnectionAttributes = function getConnectionAttributes(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available connection attributes // Retrieve available connection attributes
return requestService({ return authenticationService.request({
cache : cacheService.schema, cache : cacheService.schema,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionAttributes', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionAttributes'
params : httpParameters
}); });
}; };
@@ -151,17 +133,11 @@ angular.module('rest').factory('schemaService', ['$injector',
*/ */
service.getSharingProfileAttributes = function getSharingProfileAttributes(dataSource) { service.getSharingProfileAttributes = function getSharingProfileAttributes(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available sharing profile attributes // Retrieve available sharing profile attributes
return requestService({ return authenticationService.request({
cache : cacheService.schema, cache : cacheService.schema,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/sharingProfileAttributes', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/sharingProfileAttributes'
params : httpParameters
}); });
}; };
@@ -185,17 +161,11 @@ angular.module('rest').factory('schemaService', ['$injector',
*/ */
service.getConnectionGroupAttributes = function getConnectionGroupAttributes(dataSource) { service.getConnectionGroupAttributes = function getConnectionGroupAttributes(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available connection group attributes // Retrieve available connection group attributes
return requestService({ return authenticationService.request({
cache : cacheService.schema, cache : cacheService.schema,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionGroupAttributes', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/connectionGroupAttributes'
params : httpParameters
}); });
}; };
@@ -216,17 +186,11 @@ angular.module('rest').factory('schemaService', ['$injector',
*/ */
service.getProtocols = function getProtocols(dataSource) { service.getProtocols = function getProtocols(dataSource) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve available protocols // Retrieve available protocols
return requestService({ return authenticationService.request({
cache : cacheService.schema, cache : cacheService.schema,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/protocols', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/schema/protocols'
params : httpParameters
}); });
}; };

View File

@@ -49,17 +49,11 @@ angular.module('rest').factory('sharingProfileService', ['$injector',
*/ */
service.getSharingProfile = function getSharingProfile(dataSource, id) { service.getSharingProfile = function getSharingProfile(dataSource, id) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve sharing profile // Retrieve sharing profile
return requestService({ return authenticationService.request({
cache : cacheService.connections, cache : cacheService.connections,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id)
params : httpParameters
}); });
}; };
@@ -78,17 +72,11 @@ angular.module('rest').factory('sharingProfileService', ['$injector',
*/ */
service.getSharingProfileParameters = function getSharingProfileParameters(dataSource, id) { service.getSharingProfileParameters = function getSharingProfileParameters(dataSource, id) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve sharing profile parameters // Retrieve sharing profile parameters
return requestService({ return authenticationService.request({
cache : cacheService.connections, cache : cacheService.connections,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id) + '/parameters', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(id) + '/parameters'
params : httpParameters
}); });
}; };
@@ -109,17 +97,11 @@ angular.module('rest').factory('sharingProfileService', ['$injector',
*/ */
service.saveSharingProfile = function saveSharingProfile(dataSource, sharingProfile) { 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 sharing profile is new, add it and set the identifier automatically
if (!sharingProfile.identifier) { if (!sharingProfile.identifier) {
return requestService({ return authenticationService.request({
method : 'POST', method : 'POST',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles',
params : httpParameters,
data : sharingProfile data : sharingProfile
}) })
@@ -136,10 +118,9 @@ angular.module('rest').factory('sharingProfileService', ['$injector',
// Otherwise, update the existing sharing profile // Otherwise, update the existing sharing profile
else { else {
return requestService({ return authenticationService.request({
method : 'PUT', method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier),
params : httpParameters,
data : sharingProfile data : sharingProfile
}) })
@@ -168,16 +149,10 @@ angular.module('rest').factory('sharingProfileService', ['$injector',
*/ */
service.deleteSharingProfile = function deleteSharingProfile(dataSource, sharingProfile) { service.deleteSharingProfile = function deleteSharingProfile(dataSource, sharingProfile) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete sharing profile // Delete sharing profile
return requestService({ return authenticationService.request({
method : 'DELETE', method : 'DELETE',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/sharingProfiles/' + encodeURIComponent(sharingProfile.identifier)
params : httpParameters
}) })
// Clear the cache // Clear the cache

View File

@@ -65,16 +65,10 @@ angular.module('rest').factory('tunnelService', ['$injector',
*/ */
service.getTunnels = function getTunnels() { service.getTunnels = function getTunnels() {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve tunnels // Retrieve tunnels
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/tunnels', url : 'api/session/tunnels'
params : httpParameters
}); });
}; };
@@ -124,17 +118,11 @@ angular.module('rest').factory('tunnelService', ['$injector',
*/ */
service.getSharingProfiles = function getSharingProfiles(tunnel) { service.getSharingProfiles = function getSharingProfiles(tunnel) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve all associated sharing profiles // Retrieve all associated sharing profiles
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/tunnels/' + encodeURIComponent(tunnel) url : 'api/session/tunnels/' + encodeURIComponent(tunnel)
+ '/activeConnection/connection/sharingProfiles', + '/activeConnection/connection/sharingProfiles'
params : httpParameters
}); });
}; };
@@ -160,18 +148,12 @@ angular.module('rest').factory('tunnelService', ['$injector',
*/ */
service.getSharingCredentials = function getSharingCredentials(tunnel, sharingProfile) { service.getSharingCredentials = function getSharingCredentials(tunnel, sharingProfile) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Generate sharing credentials // Generate sharing credentials
return requestService({ return authenticationService.request({
method : 'GET', method : 'GET',
url : 'api/session/tunnels/' + encodeURIComponent(tunnel) url : 'api/session/tunnels/' + encodeURIComponent(tunnel)
+ '/activeConnection/sharingCredentials/' + '/activeConnection/sharingCredentials/'
+ encodeURIComponent(sharingProfile), + encodeURIComponent(sharingProfile)
params : httpParameters
}); });
}; };

View File

@@ -52,17 +52,13 @@ angular.module('rest').factory('userGroupService', ['$injector',
*/ */
service.getUserGroups = function getUserGroups(dataSource, permissionTypes) { service.getUserGroups = function getUserGroups(dataSource, permissionTypes) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Add permission filter if specified // Add permission filter if specified
var httpParameters = {};
if (permissionTypes) if (permissionTypes)
httpParameters.permission = permissionTypes; httpParameters.permission = permissionTypes;
// Retrieve user groups // Retrieve user groups
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups',
@@ -89,17 +85,11 @@ angular.module('rest').factory('userGroupService', ['$injector',
*/ */
service.getUserGroup = function getUserGroup(dataSource, identifier) { service.getUserGroup = function getUserGroup(dataSource, identifier) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve user group // Retrieve user group
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(identifier)
params : httpParameters
}); });
}; };
@@ -122,16 +112,10 @@ angular.module('rest').factory('userGroupService', ['$injector',
*/ */
service.deleteUserGroup = function deleteUserGroup(dataSource, userGroup) { service.deleteUserGroup = function deleteUserGroup(dataSource, userGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete user group // Delete user group
return requestService({ return authenticationService.request({
method : 'DELETE', method : 'DELETE',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier)
params : httpParameters
}) })
// Clear the cache // Clear the cache
@@ -160,16 +144,10 @@ angular.module('rest').factory('userGroupService', ['$injector',
*/ */
service.createUserGroup = function createUserGroup(dataSource, userGroup) { service.createUserGroup = function createUserGroup(dataSource, userGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Create user group // Create user group
return requestService({ return authenticationService.request({
method : 'POST', method : 'POST',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups',
params : httpParameters,
data : userGroup data : userGroup
}) })
@@ -198,16 +176,10 @@ angular.module('rest').factory('userGroupService', ['$injector',
*/ */
service.saveUserGroup = function saveUserGroup(dataSource, userGroup) { service.saveUserGroup = function saveUserGroup(dataSource, userGroup) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update user group // Update user group
return requestService({ return authenticationService.request({
method : 'PUT', method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups/' + encodeURIComponent(userGroup.identifier),
params : httpParameters,
data : userGroup data : userGroup
}) })

View File

@@ -56,17 +56,13 @@ angular.module('rest').factory('userService', ['$injector',
*/ */
service.getUsers = function getUsers(dataSource, permissionTypes) { service.getUsers = function getUsers(dataSource, permissionTypes) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Add permission filter if specified // Add permission filter if specified
var httpParameters = {};
if (permissionTypes) if (permissionTypes)
httpParameters.permission = permissionTypes; httpParameters.permission = permissionTypes;
// Retrieve users // Retrieve users
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users',
@@ -93,17 +89,11 @@ angular.module('rest').factory('userService', ['$injector',
*/ */
service.getUser = function getUser(dataSource, username) { service.getUser = function getUser(dataSource, username) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve user // Retrieve user
return requestService({ return authenticationService.request({
cache : cacheService.users, cache : cacheService.users,
method : 'GET', method : 'GET',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username)
params : httpParameters
}); });
}; };
@@ -126,16 +116,10 @@ angular.module('rest').factory('userService', ['$injector',
*/ */
service.deleteUser = function deleteUser(dataSource, user) { service.deleteUser = function deleteUser(dataSource, user) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Delete user // Delete user
return requestService({ return authenticationService.request({
method : 'DELETE', method : 'DELETE',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username)
params : httpParameters
}) })
// Clear the cache // Clear the cache
@@ -164,16 +148,10 @@ angular.module('rest').factory('userService', ['$injector',
*/ */
service.createUser = function createUser(dataSource, user) { service.createUser = function createUser(dataSource, user) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Create user // Create user
return requestService({ return authenticationService.request({
method : 'POST', method : 'POST',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users',
params : httpParameters,
data : user data : user
}) })
@@ -202,16 +180,10 @@ angular.module('rest').factory('userService', ['$injector',
*/ */
service.saveUser = function saveUser(dataSource, user) { service.saveUser = function saveUser(dataSource, user) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update user // Update user
return requestService({ return authenticationService.request({
method : 'PUT', method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username), url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(user.username),
params : httpParameters,
data : user data : user
}) })
@@ -247,16 +219,10 @@ angular.module('rest').factory('userService', ['$injector',
service.updateUserPassword = function updateUserPassword(dataSource, username, service.updateUserPassword = function updateUserPassword(dataSource, username,
oldPassword, newPassword) { oldPassword, newPassword) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Update user password // Update user password
return requestService({ return authenticationService.request({
method : 'PUT', method : 'PUT',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username) + '/password', url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username) + '/password',
params : httpParameters,
data : new UserPasswordUpdate({ data : new UserPasswordUpdate({
oldPassword : oldPassword, oldPassword : oldPassword,
newPassword : newPassword newPassword : newPassword

View File

@@ -21,7 +21,6 @@ package org.apache.guacamole.rest;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@@ -32,6 +31,7 @@ import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.GuacamoleUnauthorizedException;
import org.apache.guacamole.rest.auth.AuthenticationService; import org.apache.guacamole.rest.auth.AuthenticationService;
import org.glassfish.jersey.server.ContainerRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -50,12 +50,13 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
private final Logger logger = LoggerFactory.getLogger(RESTExceptionMapper.class); private final Logger logger = LoggerFactory.getLogger(RESTExceptionMapper.class);
/** /**
* The HttpServletRequest for the Throwable being intercepted. Despite this * The ContainerRequest representing the HTTP request that resulted in the
* class being a Singleton, this object will always be scoped with the * Throwable being intercepted. Despite this class being a Singleton, this
* current request for the Throwable that is being processed by this class. * object will always be scoped with the current request for the Throwable
* that is being processed by this class.
*/ */
@Context @Context
private HttpServletRequest request; private ContainerRequest request;
/** /**
* The authentication service associated with the currently active session. * The authentication service associated with the currently active session.
@@ -63,24 +64,6 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
@Inject @Inject
private AuthenticationService authenticationService; 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 @Override
public Response toResponse(Throwable t) { public Response toResponse(Throwable t) {
@@ -90,8 +73,7 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
// Ensure any associated session is invalidated if unauthorized // Ensure any associated session is invalidated if unauthorized
if (t instanceof GuacamoleUnauthorizedException) { if (t instanceof GuacamoleUnauthorizedException) {
String token = getAuthenticationToken(); String token = authenticationService.getAuthenticationToken(request);
if (authenticationService.destroyGuacamoleSession(token)) if (authenticationService.destroyGuacamoleSession(token))
logger.debug("Implicitly invalidated session for token \"{}\"", token); logger.debug("Implicitly invalidated session for token \"{}\"", token);
} }

View File

@@ -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 {}

View File

@@ -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
* <code>@TokenParam</code> 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<ContainerRequest, ?> getValueProvider(Parameter parameter) {
if (parameter.getAnnotation(TokenParam.class) == null)
return null;
return (request) -> authenticationService.getAuthenticationToken(request);
}
@Override
public PriorityType getPriority() {
return Priority.HIGH;
}
}

View File

@@ -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.AuthenticationFailureEvent;
import org.apache.guacamole.net.event.AuthenticationSuccessEvent; import org.apache.guacamole.net.event.AuthenticationSuccessEvent;
import org.apache.guacamole.rest.event.ListenerService; import org.apache.guacamole.rest.event.ListenerService;
import org.glassfish.jersey.server.ContainerRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -91,6 +92,18 @@ public class AuthenticationService {
@Inject @Inject
private ListenerService listenerService; 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. * Regular expression which matches any IPv4 address.
*/ */
@@ -541,4 +554,34 @@ public class AuthenticationService {
return getGuacamoleSession(authToken).getUserContexts(); 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;
}
} }

View File

@@ -23,10 +23,10 @@ import javax.inject.Inject;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.GuacamoleSession;
import org.apache.guacamole.rest.TokenParam;
import org.apache.guacamole.rest.auth.AuthenticationService; import org.apache.guacamole.rest.auth.AuthenticationService;
/** /**
@@ -67,12 +67,12 @@ public class SessionRESTService {
* If the authentication token is invalid. * If the authentication token is invalid.
*/ */
@Path("/") @Path("/")
public SessionResource getSessionResource(@QueryParam("token") String authToken) public SessionResource getSessionResource(@TokenParam String authToken)
throws GuacamoleException { throws GuacamoleException {
// Return a resource exposing the retrieved session // Return a resource exposing the retrieved session
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
return sessionResourceFactory.create(session); return sessionResourceFactory.create(authToken, session);
} }

View File

@@ -23,6 +23,7 @@ import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject; import com.google.inject.assistedinject.AssistedInject;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
@@ -31,6 +32,7 @@ import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException; import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.GuacamoleSession;
import org.apache.guacamole.net.auth.UserContext; 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.TunnelCollectionResource;
import org.apache.guacamole.rest.tunnel.TunnelCollectionResourceFactory; import org.apache.guacamole.rest.tunnel.TunnelCollectionResourceFactory;
@@ -47,6 +49,18 @@ public class SessionResource {
*/ */
private final GuacamoleSession session; 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 * Factory for creating UserContextResources which expose a given
* UserContext. * UserContext.
@@ -65,12 +79,16 @@ public class SessionResource {
* Creates a new SessionResource which exposes the data within the given * Creates a new SessionResource which exposes the data within the given
* GuacamoleSession. * GuacamoleSession.
* *
* @param token
* The authentication token associated with the given session.
*
* @param session * @param session
* The GuacamoleSession which should be exposed through this * The GuacamoleSession which should be exposed through this
* SessionResource. * SessionResource.
*/ */
@AssistedInject @AssistedInject
public SessionResource(@Assisted GuacamoleSession session) { public SessionResource(@Assisted String token, @Assisted GuacamoleSession session) {
this.token = token;
this.session = session; this.session = session;
} }
@@ -149,4 +167,21 @@ public class SessionResource {
return tunnelCollectionResourceFactory.create(session); 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.");
}
} }

View File

@@ -31,6 +31,9 @@ public interface SessionResourceFactory {
* Creates a new SessionResource which exposes the contents of the * Creates a new SessionResource which exposes the contents of the
* given GuacamoleSession. * given GuacamoleSession.
* *
* @param token
* The authentication token associated with the given session.
*
* @param session * @param session
* The GuacamoleSession whose contents should be exposed. * The GuacamoleSession whose contents should be exposed.
* *
@@ -38,6 +41,6 @@ public interface SessionResourceFactory {
* A new SessionResource which exposes the contents of the given * A new SessionResource which exposes the contents of the given
* GuacamoleSession. * GuacamoleSession.
*/ */
SessionResource create(GuacamoleSession session); SessionResource create(String token, GuacamoleSession session);
} }