mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
Merge pull request #118 from glyptodon/batch-delete-tunnel
GUAC-1132: Implement batch deletion of tunnels.
This commit is contained in:
@@ -28,6 +28,7 @@ import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ConnectionGroupDirector
|
||||
import org.glyptodon.guacamole.auth.jdbc.connection.ConnectionDirectory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.auth.jdbc.base.RestrictedObject;
|
||||
@@ -130,9 +131,22 @@ public class UserContext extends RestrictedObject
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionRecord getActiveConnection(String tunnelUUID)
|
||||
public Collection<ConnectionRecord> getActiveConnections(Collection<String> tunnelUUIDs)
|
||||
throws GuacamoleException {
|
||||
return tunnelService.getActiveConnection(getCurrentUser(), tunnelUUID);
|
||||
|
||||
// Look up active connections for each given tunnel UUID
|
||||
Collection<ConnectionRecord> records = new ArrayList<ConnectionRecord>(tunnelUUIDs.size());
|
||||
for (String tunnelUUID : tunnelUUIDs) {
|
||||
|
||||
// Add corresponding record only if it exists
|
||||
ConnectionRecord record = tunnelService.getActiveConnection(getCurrentUser(), tunnelUUID);
|
||||
if (record != null)
|
||||
records.add(record);
|
||||
|
||||
}
|
||||
|
||||
return records;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -113,24 +113,24 @@ public interface UserContext {
|
||||
throws GuacamoleException;
|
||||
|
||||
/**
|
||||
* Returns the connection record associated with the active connection
|
||||
* having the tunnel with the given UUID. The active connection will only
|
||||
* Returns the connection records associated with the active connections
|
||||
* having the tunnels with the given UUIDs. An active connection will only
|
||||
* be returned if the current user has access.
|
||||
*
|
||||
* @param tunnelUUID
|
||||
* The UUID of the tunnel whose associated connection record should be
|
||||
* returned.
|
||||
* @param tunnelUUIDs
|
||||
* The UUIDs of the tunnels whose associated connection records should
|
||||
* be returned.
|
||||
*
|
||||
* @return
|
||||
* The connection record associated with the active connection having
|
||||
* the tunnel with the given UUID, if any, or null if no such
|
||||
* connection exists.
|
||||
* A collection of all connection records associated with the active
|
||||
* connections having the tunnels with the given UUIDs, if any, or an
|
||||
* empty collection if no such connections exist.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while reading active connection records, or if
|
||||
* permission is denied.
|
||||
*/
|
||||
ConnectionRecord getActiveConnection(String tunnelUUID)
|
||||
Collection<ConnectionRecord> getActiveConnections(Collection<String> tunnelUUIDs)
|
||||
throws GuacamoleException;
|
||||
|
||||
}
|
||||
|
@@ -175,9 +175,9 @@ public class SimpleUserContext implements UserContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionRecord getActiveConnection(String tunnelUUID)
|
||||
public Collection<ConnectionRecord> getActiveConnections(Collection<String> tunnelUUID)
|
||||
throws GuacamoleException {
|
||||
return null;
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,22 +24,25 @@ package org.glyptodon.guacamole.net.basic.rest.tunnel;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.glyptodon.guacamole.GuacamoleClientException;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.glyptodon.guacamole.GuacamoleUnsupportedException;
|
||||
import org.glyptodon.guacamole.net.GuacamoleTunnel;
|
||||
import org.glyptodon.guacamole.net.auth.ConnectionRecord;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
import org.glyptodon.guacamole.net.basic.rest.APIPatch;
|
||||
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
|
||||
import org.glyptodon.guacamole.net.basic.rest.PATCH;
|
||||
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -74,7 +77,8 @@ public class TunnelRESTService {
|
||||
* performing the operation.
|
||||
*
|
||||
* @return
|
||||
* The tunnels of all active connections visible to the current user.
|
||||
* A map of the tunnels of all active connections visible to the
|
||||
* current user, where the key of each entry is the tunnel's UUID.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the tunnels.
|
||||
@@ -82,19 +86,21 @@ public class TunnelRESTService {
|
||||
@GET
|
||||
@Path("/")
|
||||
@AuthProviderRESTExposure
|
||||
public List<APITunnel> getTunnels(@QueryParam("token") String authToken)
|
||||
public Map<String, APITunnel> getTunnels(@QueryParam("token") String authToken)
|
||||
throws GuacamoleException {
|
||||
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
|
||||
// Retrieve all active tunnels
|
||||
List<APITunnel> apiTunnels = new ArrayList<APITunnel>();
|
||||
Map<String, APITunnel> apiTunnels = new HashMap<String, APITunnel>();
|
||||
for (ConnectionRecord record : userContext.getActiveConnections()) {
|
||||
|
||||
// Locate associated tunnel and UUID
|
||||
GuacamoleTunnel tunnel = record.getTunnel();
|
||||
if (tunnel != null)
|
||||
apiTunnels.add(new APITunnel(record, tunnel.getUUID().toString()));
|
||||
if (tunnel != null) {
|
||||
APITunnel apiTunnel = new APITunnel(record, tunnel.getUUID().toString());
|
||||
apiTunnels.put(apiTunnel.getUUID(), apiTunnel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -103,37 +109,58 @@ public class TunnelRESTService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the tunnel having the given UUID, effectively closing the
|
||||
* tunnel and killing the associated connection.
|
||||
* Applies the given tunnel patches. This operation currently only supports
|
||||
* deletion of tunnels through the "remove" patch operation. Deleting a
|
||||
* tunnel effectively closing the tunnel and kills the associated
|
||||
* connection. The path of each patch operation is of the form "/UUID"
|
||||
* where UUID is the UUID of the tunnel being modified.
|
||||
*
|
||||
* @param authToken
|
||||
* The authentication token that is used to authenticate the user
|
||||
* performing the operation.
|
||||
*
|
||||
* @param tunnelUUID
|
||||
* The UUID associated with the tunnel being deleted.
|
||||
* @param patches
|
||||
* The tunnel patches to apply for this request.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while deleting the tunnel.
|
||||
* If an error occurs while deleting the tunnels.
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{tunnelUUID}")
|
||||
@PATCH
|
||||
@Path("/")
|
||||
@AuthProviderRESTExposure
|
||||
public void deleteTunnel(@QueryParam("token") String authToken,
|
||||
@PathParam("tunnelUUID") String tunnelUUID)
|
||||
throws GuacamoleException {
|
||||
public void patchTunnels(@QueryParam("token") String authToken,
|
||||
List<APIPatch<String>> patches) throws GuacamoleException {
|
||||
|
||||
// Attempt to get all requested tunnels
|
||||
UserContext userContext = authenticationService.getUserContext(authToken);
|
||||
|
||||
// Retrieve specified tunnel
|
||||
ConnectionRecord record = userContext.getActiveConnection(tunnelUUID);
|
||||
if (record == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such tunnel: \"" + tunnelUUID + "\"");
|
||||
// Build list of tunnels to delete
|
||||
Collection<String> tunnelUUIDs = new ArrayList<String>(patches.size());
|
||||
for (APIPatch<String> patch : patches) {
|
||||
|
||||
// Close tunnel, if not already closed
|
||||
GuacamoleTunnel tunnel = record.getTunnel();
|
||||
if (tunnel != null && tunnel.isOpen())
|
||||
tunnel.close();
|
||||
// Only remove is supported
|
||||
if (patch.getOp() != APIPatch.Operation.remove)
|
||||
throw new GuacamoleUnsupportedException("Only the \"remove\" operation is supported when patching tunnels.");
|
||||
|
||||
// Retrieve and validate path
|
||||
String path = patch.getPath();
|
||||
if (!path.startsWith("/"))
|
||||
throw new GuacamoleClientException("Patch paths must start with \"/\".");
|
||||
|
||||
// Add UUID
|
||||
tunnelUUIDs.add(path.substring(1));
|
||||
|
||||
}
|
||||
|
||||
// Close each tunnel, if not already closed
|
||||
Collection<ConnectionRecord> records = userContext.getActiveConnections(tunnelUUIDs);
|
||||
for (ConnectionRecord record : records) {
|
||||
|
||||
GuacamoleTunnel tunnel = record.getTunnel();
|
||||
if (tunnel != null && tunnel.isOpen())
|
||||
tunnel.close();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -74,11 +74,11 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
|
||||
$scope.connections = {};
|
||||
|
||||
/**
|
||||
* The count of currently selected tunnel wrappers.
|
||||
* Map of all currently-selected tunnel wrappers by UUID.
|
||||
*
|
||||
* @type Number
|
||||
* @type Object.<String, ActiveTunnelWrapper>
|
||||
*/
|
||||
var selectedWrapperCount = 0;
|
||||
var selectedWrappers = {};
|
||||
|
||||
/**
|
||||
* Adds the given connection to the internal set of visible
|
||||
@@ -126,9 +126,9 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
|
||||
|
||||
// Wrap all active tunnels for sake of display
|
||||
$scope.wrappers = [];
|
||||
tunnels.forEach(function wrapActiveTunnel(tunnel) {
|
||||
$scope.wrappers.push(new ActiveTunnelWrapper(tunnel));
|
||||
});
|
||||
for (var tunnelUUID in tunnels) {
|
||||
$scope.wrappers.push(new ActiveTunnelWrapper(tunnels[tunnelUUID]));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -147,12 +147,24 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* closes the currently-shown status dialog.
|
||||
*/
|
||||
var ACKNOWLEDGE_ACTION = {
|
||||
name : "MANAGE_SESSION.ACTION_ACKNOWLEDGE",
|
||||
// Handle action
|
||||
callback : function acknowledgeCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An action to be provided along with the object sent to showStatus which
|
||||
* closes the currently-shown status dialog.
|
||||
*/
|
||||
var CANCEL_ACTION = {
|
||||
name : "MANAGE_USER.ACTION_CANCEL",
|
||||
name : "MANAGE_SESSION.ACTION_CANCEL",
|
||||
// Handle action
|
||||
callback : function cancelCallback() {
|
||||
guacNotification.showStatus(false);
|
||||
@@ -178,7 +190,31 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
|
||||
* confirmation.
|
||||
*/
|
||||
var deleteSessionsImmediately = function deleteSessionsImmediately() {
|
||||
// TODO: Use a batch delete function to delete the sessions.
|
||||
|
||||
// Perform deletion
|
||||
tunnelService.deleteActiveTunnels(Object.keys(selectedWrappers))
|
||||
.success(function tunnelsDeleted() {
|
||||
|
||||
// Remove deleted tunnels from wrapper array
|
||||
$scope.wrappers = $scope.wrappers.filter(function tunnelStillExists(wrapper) {
|
||||
return !(wrapper.tunnel.uuid in selectedWrappers);
|
||||
});
|
||||
|
||||
// Clear selection
|
||||
selectedWrappers = {};
|
||||
|
||||
})
|
||||
|
||||
// Notify of any errors
|
||||
.error(function tunnelDeletionFailed(error) {
|
||||
guacNotification.showStatus({
|
||||
'className' : 'error',
|
||||
'title' : 'MANAGE_SESSION.DIALOG_HEADER_ERROR',
|
||||
'text' : error.message,
|
||||
'actions' : [ ACKNOWLEDGE_ACTION ]
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -201,20 +237,31 @@ angular.module('manage').controller('manageSessionsController', ['$scope', '$inj
|
||||
* true if selected sessions can be deleted, false otherwise.
|
||||
*/
|
||||
$scope.canDeleteSessions = function canDeleteSessions() {
|
||||
return selectedWrapperCount > 0;
|
||||
|
||||
// We can delete sessions if at least one is selected
|
||||
for (var tunnelUUID in selectedWrappers)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Called whenever a tunnel wrapper changes selected status.
|
||||
*
|
||||
* @param {Boolean} selected
|
||||
* Whether the wrapper is now selected.
|
||||
* @param {ActiveTunnelWrapper} wrapper
|
||||
* The wrapper whose selected status has changed.
|
||||
*/
|
||||
$scope.wrapperSelectionChange = function wrapperSelectionChange(selected) {
|
||||
if (selected)
|
||||
selectedWrapperCount++;
|
||||
$scope.wrapperSelectionChange = function wrapperSelectionChange(wrapper) {
|
||||
|
||||
// Add wrapper to map if selected
|
||||
if (wrapper.checked)
|
||||
selectedWrappers[wrapper.tunnel.uuid] = wrapper;
|
||||
|
||||
// Otherwise, remove wrapper from map
|
||||
else
|
||||
selectedWrapperCount--;
|
||||
delete selectedWrappers[wrapper.tunnel.uuid];
|
||||
|
||||
};
|
||||
|
||||
}]);
|
||||
|
@@ -32,7 +32,11 @@ THE SOFTWARE.
|
||||
<div class="sessions">
|
||||
|
||||
<p>{{'MANAGE_SESSION.HELP_SESSIONS' | translate}}</p>
|
||||
<button class="delete-sessions" ng-disabled="!canDeleteSessions()" ng-click="deleteSessions()">{{'MANAGE_SESSION.ACTION_DELETE' | translate}}</button>
|
||||
|
||||
<!-- Form action buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="delete-sessions danger" ng-disabled="!canDeleteSessions()" ng-click="deleteSessions()">{{'MANAGE_SESSION.ACTION_DELETE' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<!-- List of current user sessions -->
|
||||
<table class="session-list">
|
||||
@@ -56,7 +60,7 @@ THE SOFTWARE.
|
||||
<tbody>
|
||||
<tr ng-repeat="wrapper in wrapperPage" class="session">
|
||||
<td>
|
||||
<input ng-change="wrapperSelectionChange(wrapper.checked)" type="checkbox" ng-model="wrapper.checked" />
|
||||
<input ng-change="wrapperSelectionChange(wrapper)" type="checkbox" ng-model="wrapper.checked" />
|
||||
</td>
|
||||
<td>
|
||||
{{wrapper.tunnel.username}}
|
||||
|
@@ -30,12 +30,12 @@ angular.module('rest').factory('tunnelService', ['$http', 'authenticationService
|
||||
|
||||
/**
|
||||
* Makes a request to the REST API to get the list of active tunnels,
|
||||
* returning a promise that provides an array of @link{ActiveTunnel}
|
||||
* returning a promise that provides a map of @link{ActiveTunnel}
|
||||
* objects if successful.
|
||||
*
|
||||
* @returns {Promise.<ActiveTunnel[]>}
|
||||
* A promise which will resolve with an array of @link{ActiveTunnel}
|
||||
* objects upon success.
|
||||
* @returns {Promise.<Object.<String, ActiveTunnel>>}
|
||||
* A promise which will resolve with a map of @link{ActiveTunnel}
|
||||
* objects, where each key is the UUID of the corresponding tunnel.
|
||||
*/
|
||||
service.getActiveTunnels = function getActiveTunnels() {
|
||||
|
||||
@@ -54,31 +54,41 @@ angular.module('rest').factory('tunnelService', ['$http', 'authenticationService
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a request to the REST API to delete the tunnel having the given
|
||||
* UUID, effectively disconnecting the tunnel, returning a promise that can
|
||||
* be used for processing the results of the call.
|
||||
* Makes a request to the REST API to delete the tunnels having the given
|
||||
* UUIDs, effectively disconnecting the tunnels, returning a promise that
|
||||
* can be used for processing the results of the call.
|
||||
*
|
||||
* @param {String} uuid
|
||||
* The UUID of the tunnel to delete.
|
||||
* @param {String[]} uuids
|
||||
* The UUIDs of the tunnels to delete.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* A promise for the HTTP call which will succeed if and only if the
|
||||
* delete operation is successful.
|
||||
*/
|
||||
service.deleteActiveTunnel = function deleteActiveTunnel(uuid) {
|
||||
service.deleteActiveTunnels = function deleteActiveTunnels(uuids) {
|
||||
|
||||
// Build HTTP parameters set
|
||||
var httpParameters = {
|
||||
token : authenticationService.getCurrentToken()
|
||||
};
|
||||
|
||||
// Delete connection
|
||||
return $http({
|
||||
method : 'DELETE',
|
||||
url : 'api/tunnels/' + encodeURIComponent(uuid),
|
||||
params : httpParameters
|
||||
// Convert provided array of UUIDs to a patch
|
||||
var tunnelPatch = [];
|
||||
uuids.forEach(function addTunnelPatch(uuid) {
|
||||
tunnelPatch.push({
|
||||
op : 'remove',
|
||||
path : '/' + uuid
|
||||
});
|
||||
});
|
||||
|
||||
// Perform tunnel deletion via PATCH
|
||||
return $http({
|
||||
method : 'PATCH',
|
||||
url : 'api/tunnels',
|
||||
params : httpParameters,
|
||||
data : tunnelPatch
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return service;
|
||||
|
@@ -236,12 +236,14 @@
|
||||
|
||||
"MANAGE_SESSION" : {
|
||||
|
||||
"ACTION_DELETE" : "Kill all selected sessions",
|
||||
"ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE",
|
||||
"ACTION_CANCEL" : "@:APP.ACTION_CANCEL",
|
||||
"ACTION_DELETE" : "Kill Sessions",
|
||||
|
||||
"DIALOG_HEADER_CONFIRM_DELETE" : "Kill Sessions",
|
||||
"DIALOG_HEADER_ERROR" : "Error",
|
||||
|
||||
"HELP_SESSIONS" : "Click to kill a user session.",
|
||||
"HELP_SESSIONS" : "All currently-active Guacamole sessions are listed here. If you wish to kill one or more sessions, check the box next to those sessions and click \"Kill Sessions\". Killing a session will immediately disconnect the user from the associated connection.",
|
||||
|
||||
"SECTION_HEADER_SESSIONS" : "Sessions",
|
||||
|
||||
@@ -250,7 +252,7 @@
|
||||
"TABLE_HEADER_SESSION_REMOTEHOST" : "Remote host",
|
||||
"TABLE_HEADER_SESSION_CONNECTION_NAME" : "Connection name",
|
||||
|
||||
"TEXT_CONFIRM_DELETE" : "Are you sure you want to kill these sessions?"
|
||||
"TEXT_CONFIRM_DELETE" : "Are you sure you want to kill all selected sessions? The users using these sessions will be immediately disconnected."
|
||||
|
||||
},
|
||||
|
||||
|
Reference in New Issue
Block a user