From 6d6bf889f26d4a1c3ef48ce66d64b3eb30c63281 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 11 Dec 2014 15:03:57 -0800 Subject: [PATCH 01/37] GUAC-932: Refactor existing REST service JS into single 'rest' module. --- .../main/webapp/app/client/clientModule.js | 2 +- .../connectionGroup/connectionGroupModule.js | 26 ------------------- .../src/main/webapp/app/home/homeModule.js | 2 +- .../src/main/webapp/app/index/indexModule.js | 2 +- .../main/webapp/app/manage/manageModule.js | 2 +- .../webapp/app/permission/permissionModule.js | 26 ------------------- .../webapp/app/protocol/protocolModule.js | 26 ------------------- .../restModule.js} | 5 ++-- .../services}/connectionGroupDAO.js | 2 +- .../services}/connectionGroupService.js | 2 +- .../services}/connectionService.js | 2 +- .../services/permissionCheckService.js | 2 +- .../services/permissionDAO.js | 2 +- .../services/protocolDAO.js | 2 +- .../app/{user => rest}/services/userDAO.js | 2 +- .../{user => rest}/services/userService.js | 2 +- .../{connection => rest}/types/Connection.js | 2 +- .../types/ConnectionHistoryEntry.js | 2 +- .../src/main/webapp/app/user/userModule.js | 26 ------------------- 19 files changed, 17 insertions(+), 120 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js delete mode 100644 guacamole/src/main/webapp/app/permission/permissionModule.js delete mode 100644 guacamole/src/main/webapp/app/protocol/protocolModule.js rename guacamole/src/main/webapp/app/{connection/connectionModule.js => rest/restModule.js} (89%) rename guacamole/src/main/webapp/app/{connectionGroup/service => rest/services}/connectionGroupDAO.js (98%) rename guacamole/src/main/webapp/app/{connectionGroup/service => rest/services}/connectionGroupService.js (98%) rename guacamole/src/main/webapp/app/{connection/service => rest/services}/connectionService.js (98%) rename guacamole/src/main/webapp/app/{permission => rest}/services/permissionCheckService.js (98%) rename guacamole/src/main/webapp/app/{permission => rest}/services/permissionDAO.js (98%) rename guacamole/src/main/webapp/app/{protocol => rest}/services/protocolDAO.js (94%) rename guacamole/src/main/webapp/app/{user => rest}/services/userDAO.js (98%) rename guacamole/src/main/webapp/app/{user => rest}/services/userService.js (97%) rename guacamole/src/main/webapp/app/{connection => rest}/types/Connection.js (97%) rename guacamole/src/main/webapp/app/{connection => rest}/types/ConnectionHistoryEntry.js (96%) delete mode 100644 guacamole/src/main/webapp/app/user/userModule.js diff --git a/guacamole/src/main/webapp/app/client/clientModule.js b/guacamole/src/main/webapp/app/client/clientModule.js index dec05e580..c27957115 100644 --- a/guacamole/src/main/webapp/app/client/clientModule.js +++ b/guacamole/src/main/webapp/app/client/clientModule.js @@ -23,4 +23,4 @@ /** * The module for code used to connect to a connection or balancing group. */ -angular.module('client', ['auth', 'history']); +angular.module('client', ['auth', 'history', 'rest']); diff --git a/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js b/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js deleted file mode 100644 index eab2c54a4..000000000 --- a/guacamole/src/main/webapp/app/connectionGroup/connectionGroupModule.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The module for code relating to connection groups. - */ -angular.module('connectionGroup', ['auth', 'util', 'connection']); diff --git a/guacamole/src/main/webapp/app/home/homeModule.js b/guacamole/src/main/webapp/app/home/homeModule.js index 4f7d143a3..f0eb2f0d4 100644 --- a/guacamole/src/main/webapp/app/home/homeModule.js +++ b/guacamole/src/main/webapp/app/home/homeModule.js @@ -20,4 +20,4 @@ * THE SOFTWARE. */ -angular.module('home', ['connection', 'connectionGroup', 'history', 'user', 'permission']); +angular.module('home', ['history', 'rest']); diff --git a/guacamole/src/main/webapp/app/index/indexModule.js b/guacamole/src/main/webapp/app/index/indexModule.js index 2911a14c2..b40d7ffd4 100644 --- a/guacamole/src/main/webapp/app/index/indexModule.js +++ b/guacamole/src/main/webapp/app/index/indexModule.js @@ -24,4 +24,4 @@ * The module for the root of the application. */ angular.module('index', ['ngRoute', 'pascalprecht.translate', - 'auth', 'home', 'manage', 'login', 'client', 'notification']); + 'auth', 'home', 'manage', 'login', 'client', 'notification', 'rest']); diff --git a/guacamole/src/main/webapp/app/manage/manageModule.js b/guacamole/src/main/webapp/app/manage/manageModule.js index 9ea80c415..8dfae77c6 100644 --- a/guacamole/src/main/webapp/app/manage/manageModule.js +++ b/guacamole/src/main/webapp/app/manage/manageModule.js @@ -23,5 +23,5 @@ /** * The module for the administration functionality. */ -angular.module('manage', ['btford.modal', 'protocol', 'connection', 'connectionGroup', 'util']); +angular.module('manage', ['btford.modal', 'rest', 'util']); diff --git a/guacamole/src/main/webapp/app/permission/permissionModule.js b/guacamole/src/main/webapp/app/permission/permissionModule.js deleted file mode 100644 index 524f058f5..000000000 --- a/guacamole/src/main/webapp/app/permission/permissionModule.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A module for code relating to permissions. - */ -angular.module('permission', ['auth']); diff --git a/guacamole/src/main/webapp/app/protocol/protocolModule.js b/guacamole/src/main/webapp/app/protocol/protocolModule.js deleted file mode 100644 index d6908b49c..000000000 --- a/guacamole/src/main/webapp/app/protocol/protocolModule.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The module for the protocol functionality. - */ -angular.module('protocol', []); diff --git a/guacamole/src/main/webapp/app/connection/connectionModule.js b/guacamole/src/main/webapp/app/rest/restModule.js similarity index 89% rename from guacamole/src/main/webapp/app/connection/connectionModule.js rename to guacamole/src/main/webapp/app/rest/restModule.js index 7f3b0ab88..d5a3a27d7 100644 --- a/guacamole/src/main/webapp/app/connection/connectionModule.js +++ b/guacamole/src/main/webapp/app/rest/restModule.js @@ -21,6 +21,7 @@ */ /** - * The module for code relating to connections. + * The module for code relating to communication with the REST API of the + * Guacamole web application. */ -angular.module('connection', ['auth']); +angular.module('rest', ['auth']); diff --git a/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js similarity index 98% rename from guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js rename to guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js index c2cceecb6..61cdb7580 100644 --- a/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupDAO.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js @@ -23,7 +23,7 @@ /** * The DAO for connection group operations agains the REST API. */ -angular.module('connectionGroup').factory('connectionGroupDAO', ['$http', 'authenticationService', +angular.module('rest').factory('connectionGroupDAO', ['$http', 'authenticationService', function connectionGrouDAO($http, authenticationService) { /** diff --git a/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js similarity index 98% rename from guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupService.js rename to guacamole/src/main/webapp/app/rest/services/connectionGroupService.js index 36f358760..344bba160 100644 --- a/guacamole/src/main/webapp/app/connectionGroup/service/connectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js @@ -23,7 +23,7 @@ /** * A service for performing useful connection group related functionaltiy. */ -angular.module('connectionGroup').factory('connectionGroupService', ['$injector', function connectionGroupService($injector) { +angular.module('rest').factory('connectionGroupService', ['$injector', function connectionGroupService($injector) { var connectionGroupDAO = $injector.get('connectionGroupDAO'); var connectionService = $injector.get('connectionService'); diff --git a/guacamole/src/main/webapp/app/connection/service/connectionService.js b/guacamole/src/main/webapp/app/rest/services/connectionService.js similarity index 98% rename from guacamole/src/main/webapp/app/connection/service/connectionService.js rename to guacamole/src/main/webapp/app/rest/services/connectionService.js index 42a93fb7a..1ca391d6b 100644 --- a/guacamole/src/main/webapp/app/connection/service/connectionService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionService.js @@ -23,7 +23,7 @@ /** * Service for operating on connections via the REST API. */ -angular.module('connection').factory('connectionService', ['$http', 'authenticationService', +angular.module('rest').factory('connectionService', ['$http', 'authenticationService', function connectionService($http, authenticationService) { var service = {}; diff --git a/guacamole/src/main/webapp/app/permission/services/permissionCheckService.js b/guacamole/src/main/webapp/app/rest/services/permissionCheckService.js similarity index 98% rename from guacamole/src/main/webapp/app/permission/services/permissionCheckService.js rename to guacamole/src/main/webapp/app/rest/services/permissionCheckService.js index 4e3cc6c72..80ee4cd39 100644 --- a/guacamole/src/main/webapp/app/permission/services/permissionCheckService.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionCheckService.js @@ -24,7 +24,7 @@ * A service for checking if a specific permission exists * in a given list of permissions. */ -angular.module('permission').factory('permissionCheckService', [ +angular.module('rest').factory('permissionCheckService', [ function permissionCheckService() { var service = {}; diff --git a/guacamole/src/main/webapp/app/permission/services/permissionDAO.js b/guacamole/src/main/webapp/app/rest/services/permissionDAO.js similarity index 98% rename from guacamole/src/main/webapp/app/permission/services/permissionDAO.js rename to guacamole/src/main/webapp/app/rest/services/permissionDAO.js index b26e66683..fdaaa9c57 100644 --- a/guacamole/src/main/webapp/app/permission/services/permissionDAO.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionDAO.js @@ -23,7 +23,7 @@ /** * The DAO for permission operations agains the REST API. */ -angular.module('permission').factory('permissionDAO', ['$http', 'authenticationService', +angular.module('rest').factory('permissionDAO', ['$http', 'authenticationService', function permissionDAO($http, authenticationService) { var service = {}; diff --git a/guacamole/src/main/webapp/app/protocol/services/protocolDAO.js b/guacamole/src/main/webapp/app/rest/services/protocolDAO.js similarity index 94% rename from guacamole/src/main/webapp/app/protocol/services/protocolDAO.js rename to guacamole/src/main/webapp/app/rest/services/protocolDAO.js index b45fed2f5..c18da7b2e 100644 --- a/guacamole/src/main/webapp/app/protocol/services/protocolDAO.js +++ b/guacamole/src/main/webapp/app/rest/services/protocolDAO.js @@ -23,7 +23,7 @@ /** * The DAO for protocol operations agains the REST API. */ -angular.module('protocol').factory('protocolDAO', ['$http', function protocolDAO($http) { +angular.module('rest').factory('protocolDAO', ['$http', function protocolDAO($http) { var service = {}; diff --git a/guacamole/src/main/webapp/app/user/services/userDAO.js b/guacamole/src/main/webapp/app/rest/services/userDAO.js similarity index 98% rename from guacamole/src/main/webapp/app/user/services/userDAO.js rename to guacamole/src/main/webapp/app/rest/services/userDAO.js index c8ba6f376..2b4b35373 100644 --- a/guacamole/src/main/webapp/app/user/services/userDAO.js +++ b/guacamole/src/main/webapp/app/rest/services/userDAO.js @@ -23,7 +23,7 @@ /** * The DAO for connection operations agains the REST API. */ -angular.module('user').factory('userDAO', ['$http', 'authenticationService', +angular.module('rest').factory('userDAO', ['$http', 'authenticationService', function userDAO($http, authenticationService) { var service = {}; diff --git a/guacamole/src/main/webapp/app/user/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js similarity index 97% rename from guacamole/src/main/webapp/app/user/services/userService.js rename to guacamole/src/main/webapp/app/rest/services/userService.js index ea1cb633e..afe945dc5 100644 --- a/guacamole/src/main/webapp/app/user/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -23,7 +23,7 @@ /** * A service for performing useful user related functionaltiy. */ -angular.module('user').factory('userService', ['$injector', function userService($injector) { +angular.module('rest').factory('userService', ['$injector', function userService($injector) { var permissionCheckService = $injector.get('permissionCheckService'); diff --git a/guacamole/src/main/webapp/app/connection/types/Connection.js b/guacamole/src/main/webapp/app/rest/types/Connection.js similarity index 97% rename from guacamole/src/main/webapp/app/connection/types/Connection.js rename to guacamole/src/main/webapp/app/rest/types/Connection.js index 59ed68c91..ab5aa8eca 100644 --- a/guacamole/src/main/webapp/app/connection/types/Connection.js +++ b/guacamole/src/main/webapp/app/rest/types/Connection.js @@ -23,7 +23,7 @@ /** * Service which defines the Connection class. */ -angular.module('connection').factory('Connection', [function defineConnection() { +angular.module('rest').factory('Connection', [function defineConnection() { /** * The object returned by REST API calls when representing the data diff --git a/guacamole/src/main/webapp/app/connection/types/ConnectionHistoryEntry.js b/guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js similarity index 96% rename from guacamole/src/main/webapp/app/connection/types/ConnectionHistoryEntry.js rename to guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js index 7409e52b9..9f0023323 100644 --- a/guacamole/src/main/webapp/app/connection/types/ConnectionHistoryEntry.js +++ b/guacamole/src/main/webapp/app/rest/types/ConnectionHistoryEntry.js @@ -23,7 +23,7 @@ /** * Service which defines the ConnectionHistoryEntry class. */ -angular.module('connection').factory('ConnectionHistoryEntry', [function defineConnectionHistoryEntry() { +angular.module('rest').factory('ConnectionHistoryEntry', [function defineConnectionHistoryEntry() { /** * The object returned by REST API calls when representing the data diff --git a/guacamole/src/main/webapp/app/user/userModule.js b/guacamole/src/main/webapp/app/user/userModule.js deleted file mode 100644 index abfef1c13..000000000 --- a/guacamole/src/main/webapp/app/user/userModule.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A module for code relating to users. - */ -angular.module('user', ['auth']); From 4ebb66c40d9a1572f1fce478deb869500de3cb9d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 11 Dec 2014 15:12:45 -0800 Subject: [PATCH 02/37] GUAC-932: Rename various *DAO to *Service. Rename legacy services to legacy*, pending removal/refactor. --- .../client/controllers/clientController.js | 6 +- .../app/home/controllers/homeController.js | 4 +- .../app/index/controllers/indexController.js | 4 +- .../connectionGroupEditModalController.js | 8 +- .../manage/controllers/manageController.js | 20 +- .../controllers/userEditModalController.js | 12 +- .../app/rest/services/connectionGroupDAO.js | 130 --------- .../rest/services/connectionGroupService.js | 265 ++++++------------ .../services/legacyConnectionGroupService.js | 231 +++++++++++++++ .../app/rest/services/legacyUserService.js | 57 ++++ ...{permissionDAO.js => permissionService.js} | 6 +- .../{protocolDAO.js => protocolService.js} | 4 +- .../main/webapp/app/rest/services/userDAO.js | 100 ------- .../webapp/app/rest/services/userService.js | 89 ++++-- 14 files changed, 468 insertions(+), 468 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js create mode 100644 guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js create mode 100644 guacamole/src/main/webapp/app/rest/services/legacyUserService.js rename guacamole/src/main/webapp/app/rest/services/{permissionDAO.js => permissionService.js} (95%) rename guacamole/src/main/webapp/app/rest/services/{protocolDAO.js => protocolService.js} (90%) delete mode 100644 guacamole/src/main/webapp/app/rest/services/userDAO.js diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index be93a559b..1cd7aac57 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -132,8 +132,8 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', remaining: 15 }; - // Get DAO for reading connections and groups - var connectionGroupDAO = $injector.get('connectionGroupDAO'); + // Get services for reading connections and groups + var connectionGroupService = $injector.get('connectionGroupService'); var connectionService = $injector.get('connectionService'); var ClientProperties = $injector.get('ClientProperties'); @@ -176,7 +176,7 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', // Connection group case 'g': - connectionGroupDAO.getConnectionGroup($routeParams.id).success(function (group) { + connectionGroupService.getConnectionGroup($routeParams.id).success(function (group) { $scope.connectionName = $scope.page.title = group.name; }); break; diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index 303ff9e5a..ed3cb09b1 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -30,7 +30,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector', var GUAC_HISTORY_STORAGE_KEY = "GUAC_HISTORY"; // Get the dependencies commonJS style - var connectionGroupService = $injector.get("connectionGroupService"); + var legacyConnectionGroupService = $injector.get("legacyConnectionGroupService"); var guacHistory = $injector.get("guacHistory"); // All the connections and connection groups in root @@ -45,7 +45,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector', /* Fetch all connections and groups, then find which recent connections * still refer to valid connections and groups. */ - connectionGroupService.getAllGroupsAndConnections($scope.connectionsAndGroups) + legacyConnectionGroupService.getAllGroupsAndConnections($scope.connectionsAndGroups) .then(function findRecentConnections() { // TODONT: Munch the guacHistory recentConnections list into a legacy-style object diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 5854cf7ff..cc256fc38 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -27,7 +27,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', function indexController($scope, $injector) { // Get the dependencies commonJS style - var permissionDAO = $injector.get("permissionDAO"), + var permissionService = $injector.get("permissionService"), permissionCheckService = $injector.get("permissionCheckService"), authenticationService = $injector.get("authenticationService"), $q = $injector.get("$q"), @@ -166,7 +166,7 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Allow the permissions to be reloaded elsewhere if needed $scope.loadBasicPermissions = function loadBasicPermissions() { - permissionDAO.getPermissions($scope.currentUserID).success(function fetchCurrentUserPermissions(permissions) { + permissionService.getPermissions($scope.currentUserID).success(function fetchCurrentUserPermissions(permissions) { $scope.currentUserPermissions = permissions; // Will be true if the user is an admin diff --git a/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js index 7ab53779b..df4ead022 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/connectionGroupEditModalController.js @@ -27,7 +27,7 @@ angular.module('manage').controller('connectionGroupEditModalController', ['$sco function connectionEditModalController($scope, $injector) { var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); - var connectionGroupDAO = $injector.get('connectionGroupDAO'); + var connectionGroupService = $injector.get('connectionGroupService'); var displayObjectPreparationService = $injector.get('displayObjectPreparationService'); // Make a copy of the old connection group so that we can copy over the changes when done @@ -64,7 +64,7 @@ angular.module('manage').controller('connectionGroupEditModalController', ['$sco * Save the connection and close the modal. */ $scope.save = function save() { - connectionGroupDAO.saveConnectionGroup($scope.connectionGroup).success(function successfullyUpdatedConnectionGroup() { + connectionGroupService.saveConnectionGroup($scope.connectionGroup).success(function successfullyUpdatedConnectionGroup() { // Prepare this connection group for display displayObjectPreparationService.prepareConnectionGroup($scope.connectionGroup); @@ -79,7 +79,7 @@ angular.module('manage').controller('connectionGroupEditModalController', ['$sco if(newConnectionGroup && newParentID === $scope.rootGroup.identifier) { $scope.moveItem($scope.connectionGroup, oldParentID, newParentID); } else { - connectionGroupDAO.moveConnectionGroup($scope.connectionGroup).then(function moveConnectionGroup() { + connectionGroupService.moveConnectionGroup($scope.connectionGroup).then(function moveConnectionGroup() { $scope.moveItem($scope.connectionGroup, oldParentID, newParentID); }); } @@ -99,7 +99,7 @@ angular.module('manage').controller('connectionGroupEditModalController', ['$sco // Close the modal connectionGroupEditModal.deactivate(); - connectionGroupDAO.deleteConnectionGroup($scope.connectionGroup).success(function successfullyDeletedConnectionGroup() { + connectionGroupService.deleteConnectionGroup($scope.connectionGroup).success(function successfullyDeletedConnectionGroup() { var oldParentID = oldConnectionGroup.parentIdentifier; // We have to remove this connection group from the heirarchy diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 28bed857b..32b37c4c8 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -27,13 +27,13 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', function manageController($scope, $injector) { // Get the dependencies commonJS style - var connectionGroupService = $injector.get('connectionGroupService'); + var legacyConnectionGroupService = $injector.get('legacyConnectionGroupService'); var connectionEditModal = $injector.get('connectionEditModal'); var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); var userEditModal = $injector.get('userEditModal'); - var protocolDAO = $injector.get('protocolDAO'); - var userDAO = $injector.get('userDAO'); - var userService = $injector.get('userService'); + var protocolService = $injector.get('protocolService'); + var userService = $injector.get('userService'); + var legacyUserService = $injector.get('legacyUserService'); // Set status to loading until we have all the connections, groups, and users have loaded $scope.loadingUsers = true; @@ -46,13 +46,13 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', $scope.users = []; $scope.basicPermissionsLoaded.then(function basicPermissionsHaveBeenLoaded() { - connectionGroupService.getAllGroupsAndConnections([], undefined, true, true).then(function filterConnectionsAndGroups(rootGroupList) { + legacyConnectionGroupService.getAllGroupsAndConnections([], undefined, true, true).then(function filterConnectionsAndGroups(rootGroupList) { $scope.rootGroup = rootGroupList[0]; $scope.connectionsAndGroups = $scope.rootGroup.children; // Filter the items to only include ones that we have UPDATE for if(!$scope.currentUserIsAdmin) { - connectionGroupService.filterConnectionsAndGroupByPermission( + legacyConnectionGroupService.filterConnectionsAndGroupByPermission( $scope.connectionsAndGroups, $scope.currentUserPermissions, { @@ -65,12 +65,12 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', $scope.loadingConnections = false; }); - userDAO.getUsers().success(function filterEditableUsers(users) { + userService.getUsers().success(function filterEditableUsers(users) { $scope.users = users; // Filter the users to only include ones that we have UPDATE for if(!$scope.currentUserIsAdmin) { - userService.filterUsersByPermission( + legacyUserService.filterUsersByPermission( $scope.users, $scope.currentUserPermissions, 'UPDATE' @@ -132,7 +132,7 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', $scope.protocols = {}; // Get the protocol information from the server and copy it into the scope - protocolDAO.getProtocols().success(function fetchProtocols(protocols) { + protocolService.getProtocols().success(function fetchProtocols(protocols) { angular.extend($scope.protocols, protocols); }); @@ -236,7 +236,7 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', username: $scope.newUsername }; - userDAO.createUser(newUser).success(function addUserToList() { + userService.createUser(newUser).success(function addUserToList() { $scope.users.push(newUser); }); diff --git a/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js b/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js index e53598089..db018f8df 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/userEditModalController.js @@ -27,8 +27,8 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje function userEditModalController($scope, $injector) { var userEditModal = $injector.get('userEditModal'); - var userDAO = $injector.get('userDAO'); - var permissionDAO = $injector.get('permissionDAO'); + var userService = $injector.get('userService'); + var permissionService = $injector.get('permissionService'); // Make a copy of the old user so that we can copy over the changes when done var oldUser = $scope.user; @@ -73,7 +73,7 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje return; } - userDAO.saveUser($scope.user).success(function successfullyUpdatedUser() { + userService.saveUser($scope.user).success(function successfullyUpdatedUser() { //Figure out what permissions have changed var connectionPermissionsToCreate = [], @@ -182,7 +182,7 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje if(permissionsToAdd.length || permissionsToRemove.length) { // Make the call to update the permissions - permissionDAO.patchPermissions( + permissionService.patchPermissions( $scope.user.username, permissionsToAdd, permissionsToRemove) .success(completeSaveProcess).error(handleFailure); } else { @@ -205,7 +205,7 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje originalSystemPermissions; // Get the permissions for the user we are editing - permissionDAO.getPermissions($scope.user.username).success(function gotPermissions(permissions) { + permissionService.getPermissions($scope.user.username).success(function gotPermissions(permissions) { $scope.permissions = permissions; // Figure out if the user has any system level permissions @@ -239,7 +239,7 @@ angular.module('manage').controller('userEditModalController', ['$scope', '$inje * Delete the user and close the modal. */ $scope['delete'] = function deleteUser() { - userDAO.deleteUser($scope.user).success(function successfullyDeletedUser() { + userService.deleteUser($scope.user).success(function successfullyDeletedUser() { // Remove the user from the list $scope.removeUser($scope.user); diff --git a/guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js deleted file mode 100644 index 61cdb7580..000000000 --- a/guacamole/src/main/webapp/app/rest/services/connectionGroupDAO.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The DAO for connection group operations agains the REST API. - */ -angular.module('rest').factory('connectionGroupDAO', ['$http', 'authenticationService', - function connectionGrouDAO($http, authenticationService) { - - /** - * The ID of the root connection group. - */ - var ROOT_CONNECTION_GROUP_ID = "ROOT"; - - var service = {}; - - /** - * Makes a request to the REST API to get the list of connection groups, - * returning a promise that can be used for processing the results of the call. - * - * @param {string} parentID The parent ID for the connection group. - * If not passed in, it will query a list of the - * connection groups in the root group. - * - * @returns {promise} A promise for the HTTP call. - */ - service.getConnectionGroups = function getConnectionGroups(parentID) { - - var parentIDParam = ""; - if(parentID !== undefined) - parentIDParam = "&parentID=" + parentID; - - return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam); - }; - - /** - * Makes a request to the REST API to get an individual connection group, - * returning a promise that can be used for processing the results of the call. - * - * @param {string} connectionGroupID The ID for the connection group. - * If not passed in, it will query the - * root connection group. - * - * @returns {promise} A promise for the HTTP call. - */ - service.getConnectionGroup = function getConnectionGroup(connectionGroupID) { - - // Use the root connection group ID if no ID is passed in - connectionGroupID = connectionGroupID || ROOT_CONNECTION_GROUP_ID; - - return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken()); - }; - - /** - * Makes a request to the REST API to save a connection group, - * returning a promise that can be used for processing the results of the call. - * - * @param {object} connectionGroup The connection group to update - * - * @returns {promise} A promise for the HTTP call. - */ - service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) { - // This is a new connection group - if(!connectionGroup.identifier) { - return $http.post("api/connectionGroup/?token=" + authenticationService.getCurrentToken(), connectionGroup).success( - function setConnectionGroupID(connectionGroupID){ - // Set the identifier on the new connection - connectionGroup.identifier = connectionGroupID; - return connectionGroupID; - }); - } else { - return $http.post( - "api/connectionGroup/" + connectionGroup.identifier + - "?token=" + authenticationService.getCurrentToken(), - connectionGroup); - } - }; - - /** - * Makes a request to the REST API to move a connection group to a different group, - * returning a promise that can be used for processing the results of the call. - * - * @param {object} connectionGroup The connection group to move. - * - * @returns {promise} A promise for the HTTP call. - */ - service.moveConnectionGroup = function moveConnectionGroup(connectionGroup) { - - return $http.put( - "api/connectionGroup/" + connectionGroup.identifier + - "?token=" + authenticationService.getCurrentToken() + - "&parentID=" + connectionGroup.parentIdentifier, - connectionGroup); - }; - - /** - * Makes a request to the REST API to delete a connection group, - * returning a promise that can be used for processing the results of the call. - * - * @param {object} connectionGroup The connection group to delete - * - * @returns {promise} A promise for the HTTP call. - */ - service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) { - return $http['delete']( - "api/connectionGroup/" + connectionGroup.identifier + - "?token=" + authenticationService.getCurrentToken()); - }; - - return service; -}]); diff --git a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js index 344bba160..41846cb89 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js @@ -21,210 +21,109 @@ */ /** - * A service for performing useful connection group related functionaltiy. + * Service for operating on connection groups via the REST API. */ -angular.module('rest').factory('connectionGroupService', ['$injector', function connectionGroupService($injector) { +angular.module('rest').factory('connectionGroupService', ['$http', 'authenticationService', + function connectionGrouService($http, authenticationService) { - var connectionGroupDAO = $injector.get('connectionGroupDAO'); - var connectionService = $injector.get('connectionService'); - var permissionCheckService = $injector.get('permissionCheckService'); - var $q = $injector.get('$q'); - var displayObjectPreparationService = $injector.get('displayObjectPreparationService'); + /** + * The ID of the root connection group. + */ + var ROOT_CONNECTION_GROUP_ID = "ROOT"; var service = {}; - - // Add all groups from this group to the parent group child list - function addToParent(connectionGroup, parentGroup, context, includeConnections) { - - // Include connections by default - if(typeof includeConnections === 'undefined') - includeConnections = true; - - parentGroup.children.push(connectionGroup); - - // Prepare this group for display - displayObjectPreparationService.prepareConnectionGroup(connectionGroup); - - if(includeConnections) { - // Get all connections in the group and add them under this connection group - context.openRequest(); - connectionService.getConnections(connectionGroup.identifier).success(function fetchConnections(connections) { - for(var i = 0; i < connections.length; i++) { - connections[i].isConnection = true; - connectionGroup.children.push(connections[i]); - } - context.closeRequest(); - }); - } - - // Get all connection groups in the group and repeat - context.openRequest(); - connectionGroupDAO.getConnectionGroups(connectionGroup.identifier).success(function fetchConnectionGroups(connectionGroups) { - for(var i = 0; i < connectionGroups.length; i++) { - addToParent(connectionGroups[i], connectionGroup, context, includeConnections); - } - context.closeRequest(); - }); - } /** - * Queries all connections and connection groups under the connection group - * with the provided parent ID, and returns them in a heirarchical structure - * with convinient display properties set on the objects. - * - * @param {array} items The root list of connections and groups. Should be an - * initally empty array that will get filled in as the - * connections and groups are loaded. + * Makes a request to the REST API to get the list of connection groups, + * returning a promise that can be used for processing the results of the call. * * @param {string} parentID The parent ID for the connection group. - * If not passed in, it will begin with - * the root connection group. - * - * @param {boolean} includeConnections Whether or not to include connections - * in the structure. Defaults to true. - * - * @param {boolean} includeRoot Whether or not to include the root connection group - * in the structure. Defaults to false. + * If not passed in, it will query a list of the + * connection groups in the root group. * - * @return {promise} A promise that will be fulfilled when all connections - * and groups have been loaded. + * @returns {promise} A promise for the HTTP call. */ - service.getAllGroupsAndConnections = function getAllGroupsAndConnections(items, parentID, includeConnections, includeRoot) { + service.getConnectionGroups = function getConnectionGroups(parentID) { - // Include connections by default - if(typeof includeConnections === 'undefined') - includeConnections = true; + var parentIDParam = ""; + if(parentID !== undefined) + parentIDParam = "&parentID=" + parentID; - var context = { - // The number of requets to the server currently open - openRequests : 0, - - // Create the promise - finishedFetching : $q.defer(), - - // Notify the caller that the promise has been completed - complete : function complete() { - this.finishedFetching.resolve(items); - }, - - /** - * Indicate that a request has been started. - */ - openRequest : function openRequest() { - this.openRequests++; - }, - - /** - * Indicate that a request has been completed. If this was the last - * open request, fulfill the promise. - */ - closeRequest : function closeRequest() { - if(--this.openRequests === 0) - this.complete(); - } - }; - - // Include the root only if it was asked for - if(includeRoot) { - context.openRequest(); - connectionGroupDAO.getConnectionGroup(parentID).success(function setRootGroup (rootGroup) { - items.push(rootGroup); - rootGroup.children = []; - getChildrenOfRootGroup(rootGroup.children); - context.closeRequest(); - }); - } else { - getChildrenOfRootGroup(items); - } - - // Get the children of the root group - function getChildrenOfRootGroup(children) { - context.openRequest(); - connectionGroupDAO.getConnectionGroups(parentID).success(function fetchRootConnectionGroups(connectionGroups) { - for(var i = 0; i < connectionGroups.length; i++) { - addToParent(connectionGroups[i], {children: children}, context, includeConnections); - } - - if(includeConnections) { - // Get all connections in the root group and add them under this connection group - context.openRequest(); - connectionService.getConnections().success(function fetchRootConnections(connections) { - for(var i = 0; i < connections.length; i++) { - - // Prepare this connection for display - displayObjectPreparationService.prepareConnection(connections[i]); - - children.push(connections[i]); - } - context.closeRequest(); - }); - } - - context.closeRequest(); - }); - } - - // Return the promise - return context.finishedFetching.promise; + return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam); }; + /** + * Makes a request to the REST API to get an individual connection group, + * returning a promise that can be used for processing the results of the call. + * + * @param {string} connectionGroupID The ID for the connection group. + * If not passed in, it will query the + * root connection group. + * + * @returns {promise} A promise for the HTTP call. + */ + service.getConnectionGroup = function getConnectionGroup(connectionGroupID) { + + // Use the root connection group ID if no ID is passed in + connectionGroupID = connectionGroupID || ROOT_CONNECTION_GROUP_ID; + + return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken()); + }; /** - * Filters the list of connections and groups using the provided permissions. + * Makes a request to the REST API to save a connection group, + * returning a promise that can be used for processing the results of the call. * - * @param {array} items The heirarchical list of groups and connections. - * - * @param {object} permissionList The list of permissions to use - * when filtering. - * - * @param {object} permissionCriteria A map of object type to permission type(s) - * required for that object type. + * @param {object} connectionGroup The connection group to update * - * @return {array} The filtered list. + * @returns {promise} A promise for the HTTP call. */ - service.filterConnectionsAndGroupByPermission = function filterConnectionsAndGroupByPermission(items, permissionList, permissionCriteria) { - var requiredConnectionPermission = permissionCriteria.CONNECTION; - var requiredConnectionGroupPermission = permissionCriteria.CONNECTION_GROUP; - - for(var i = 0; i < items.length; i++) { - var item = items[i]; - - if(item.isConnection && requiredConnectionPermission) { - - /* - * If item is a connection and a permission is required for this - * item, check now to see if the permission exists. If not, - * remove the item. - */ - if(!permissionCheckService.checkPermission(permissionList, - "CONNECTION", item.identifier, requiredConnectionPermission)) { - items.splice(i, 1); - continue; - } - } - else { - - /* - * If item is a group and a permission is required for this - * item, check now to see if the permission exists. If not, - * remove the item. - */ - if(requiredConnectionGroupPermission) { - if(!permissionCheckService.checkPermission(permissionList, - "CONNECTION_GROUP", item.identifier, requiredConnectionGroupPermission)) { - items.splice(i, 1); - continue; - } - } - - // Filter the children of this connection group as well - if(item.children && item.children.length) - service.filterConnectionsAndGroupByPermission(items.children); - } + service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) { + // This is a new connection group + if(!connectionGroup.identifier) { + return $http.post("api/connectionGroup/?token=" + authenticationService.getCurrentToken(), connectionGroup).success( + function setConnectionGroupID(connectionGroupID){ + // Set the identifier on the new connection + connectionGroup.identifier = connectionGroupID; + return connectionGroupID; + }); + } else { + return $http.post( + "api/connectionGroup/" + connectionGroup.identifier + + "?token=" + authenticationService.getCurrentToken(), + connectionGroup); } + }; + + /** + * Makes a request to the REST API to move a connection group to a different group, + * returning a promise that can be used for processing the results of the call. + * + * @param {object} connectionGroup The connection group to move. + * + * @returns {promise} A promise for the HTTP call. + */ + service.moveConnectionGroup = function moveConnectionGroup(connectionGroup) { - return items; - + return $http.put( + "api/connectionGroup/" + connectionGroup.identifier + + "?token=" + authenticationService.getCurrentToken() + + "&parentID=" + connectionGroup.parentIdentifier, + connectionGroup); + }; + + /** + * Makes a request to the REST API to delete a connection group, + * returning a promise that can be used for processing the results of the call. + * + * @param {object} connectionGroup The connection group to delete + * + * @returns {promise} A promise for the HTTP call. + */ + service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) { + return $http['delete']( + "api/connectionGroup/" + connectionGroup.identifier + + "?token=" + authenticationService.getCurrentToken()); }; return service; diff --git a/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js new file mode 100644 index 000000000..a9e4e71b3 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A service for performing useful connection group related functionaltiy. + */ +angular.module('rest').factory('legacyConnectionGroupService', ['$injector', function legacyConnectionGroupService($injector) { + + var connectionGroupService = $injector.get('connectionGroupService'); + var connectionService = $injector.get('connectionService'); + var permissionCheckService = $injector.get('permissionCheckService'); + var $q = $injector.get('$q'); + var displayObjectPreparationService = $injector.get('displayObjectPreparationService'); + + var service = {}; + + // Add all groups from this group to the parent group child list + function addToParent(connectionGroup, parentGroup, context, includeConnections) { + + // Include connections by default + if(typeof includeConnections === 'undefined') + includeConnections = true; + + parentGroup.children.push(connectionGroup); + + // Prepare this group for display + displayObjectPreparationService.prepareConnectionGroup(connectionGroup); + + if(includeConnections) { + // Get all connections in the group and add them under this connection group + context.openRequest(); + connectionService.getConnections(connectionGroup.identifier).success(function fetchConnections(connections) { + for(var i = 0; i < connections.length; i++) { + connections[i].isConnection = true; + connectionGroup.children.push(connections[i]); + } + context.closeRequest(); + }); + } + + // Get all connection groups in the group and repeat + context.openRequest(); + connectionGroupService.getConnectionGroups(connectionGroup.identifier).success(function fetchConnectionGroups(connectionGroups) { + for(var i = 0; i < connectionGroups.length; i++) { + addToParent(connectionGroups[i], connectionGroup, context, includeConnections); + } + context.closeRequest(); + }); + } + + /** + * Queries all connections and connection groups under the connection group + * with the provided parent ID, and returns them in a heirarchical structure + * with convinient display properties set on the objects. + * + * @param {array} items The root list of connections and groups. Should be an + * initally empty array that will get filled in as the + * connections and groups are loaded. + * + * @param {string} parentID The parent ID for the connection group. + * If not passed in, it will begin with + * the root connection group. + * + * @param {boolean} includeConnections Whether or not to include connections + * in the structure. Defaults to true. + * + * @param {boolean} includeRoot Whether or not to include the root connection group + * in the structure. Defaults to false. + * + * @return {promise} A promise that will be fulfilled when all connections + * and groups have been loaded. + */ + service.getAllGroupsAndConnections = function getAllGroupsAndConnections(items, parentID, includeConnections, includeRoot) { + + // Include connections by default + if(typeof includeConnections === 'undefined') + includeConnections = true; + + var context = { + // The number of requets to the server currently open + openRequests : 0, + + // Create the promise + finishedFetching : $q.defer(), + + // Notify the caller that the promise has been completed + complete : function complete() { + this.finishedFetching.resolve(items); + }, + + /** + * Indicate that a request has been started. + */ + openRequest : function openRequest() { + this.openRequests++; + }, + + /** + * Indicate that a request has been completed. If this was the last + * open request, fulfill the promise. + */ + closeRequest : function closeRequest() { + if(--this.openRequests === 0) + this.complete(); + } + }; + + // Include the root only if it was asked for + if(includeRoot) { + context.openRequest(); + connectionGroupService.getConnectionGroup(parentID).success(function setRootGroup (rootGroup) { + items.push(rootGroup); + rootGroup.children = []; + getChildrenOfRootGroup(rootGroup.children); + context.closeRequest(); + }); + } else { + getChildrenOfRootGroup(items); + } + + // Get the children of the root group + function getChildrenOfRootGroup(children) { + context.openRequest(); + connectionGroupService.getConnectionGroups(parentID).success(function fetchRootConnectionGroups(connectionGroups) { + for(var i = 0; i < connectionGroups.length; i++) { + addToParent(connectionGroups[i], {children: children}, context, includeConnections); + } + + if(includeConnections) { + // Get all connections in the root group and add them under this connection group + context.openRequest(); + connectionService.getConnections().success(function fetchRootConnections(connections) { + for(var i = 0; i < connections.length; i++) { + + // Prepare this connection for display + displayObjectPreparationService.prepareConnection(connections[i]); + + children.push(connections[i]); + } + context.closeRequest(); + }); + } + + context.closeRequest(); + }); + } + + // Return the promise + return context.finishedFetching.promise; + }; + + + /** + * Filters the list of connections and groups using the provided permissions. + * + * @param {array} items The heirarchical list of groups and connections. + * + * @param {object} permissionList The list of permissions to use + * when filtering. + * + * @param {object} permissionCriteria A map of object type to permission type(s) + * required for that object type. + * + * @return {array} The filtered list. + */ + service.filterConnectionsAndGroupByPermission = function filterConnectionsAndGroupByPermission(items, permissionList, permissionCriteria) { + var requiredConnectionPermission = permissionCriteria.CONNECTION; + var requiredConnectionGroupPermission = permissionCriteria.CONNECTION_GROUP; + + for(var i = 0; i < items.length; i++) { + var item = items[i]; + + if(item.isConnection && requiredConnectionPermission) { + + /* + * If item is a connection and a permission is required for this + * item, check now to see if the permission exists. If not, + * remove the item. + */ + if(!permissionCheckService.checkPermission(permissionList, + "CONNECTION", item.identifier, requiredConnectionPermission)) { + items.splice(i, 1); + continue; + } + } + else { + + /* + * If item is a group and a permission is required for this + * item, check now to see if the permission exists. If not, + * remove the item. + */ + if(requiredConnectionGroupPermission) { + if(!permissionCheckService.checkPermission(permissionList, + "CONNECTION_GROUP", item.identifier, requiredConnectionGroupPermission)) { + items.splice(i, 1); + continue; + } + } + + // Filter the children of this connection group as well + if(item.children && item.children.length) + service.filterConnectionsAndGroupByPermission(items.children); + } + } + + return items; + + }; + + return service; +}]); diff --git a/guacamole/src/main/webapp/app/rest/services/legacyUserService.js b/guacamole/src/main/webapp/app/rest/services/legacyUserService.js new file mode 100644 index 000000000..894958a2c --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/services/legacyUserService.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A service for performing useful user related functionaltiy. + */ +angular.module('rest').factory('legacyUserService', ['$injector', function legacyUserService($injector) { + + var permissionCheckService = $injector.get('permissionCheckService'); + + var service = {}; + + /** + * Filters the list of users using the provided permissions. + * + * @param {array} users The user list. + * + * @param {object} permissionList The list of permissions to use + * when filtering. + * + * @param {object} permissionCriteria The required permission for each user. + * + * @return {array} The filtered list. + */ + service.filterUsersByPermission = function filterUsersByPermission(users, permissionList, permissionCriteria) { + for(var i = 0; i < users.length; i++) { + if(!permissionCheckService.checkPermission(permissionList, + "USER", user.username, permissionCriteria)) { + items.splice(i, 1); + continue; + } + } + + return users; + }; + + return service; +}]); diff --git a/guacamole/src/main/webapp/app/rest/services/permissionDAO.js b/guacamole/src/main/webapp/app/rest/services/permissionService.js similarity index 95% rename from guacamole/src/main/webapp/app/rest/services/permissionDAO.js rename to guacamole/src/main/webapp/app/rest/services/permissionService.js index fdaaa9c57..3ba1b2f9e 100644 --- a/guacamole/src/main/webapp/app/rest/services/permissionDAO.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionService.js @@ -21,10 +21,10 @@ */ /** - * The DAO for permission operations agains the REST API. + * Service for operating on user permissions via the REST API. */ -angular.module('rest').factory('permissionDAO', ['$http', 'authenticationService', - function permissionDAO($http, authenticationService) { +angular.module('rest').factory('permissionService', ['$http', 'authenticationService', + function permissionService($http, authenticationService) { var service = {}; diff --git a/guacamole/src/main/webapp/app/rest/services/protocolDAO.js b/guacamole/src/main/webapp/app/rest/services/protocolService.js similarity index 90% rename from guacamole/src/main/webapp/app/rest/services/protocolDAO.js rename to guacamole/src/main/webapp/app/rest/services/protocolService.js index c18da7b2e..a2a4a5d51 100644 --- a/guacamole/src/main/webapp/app/rest/services/protocolDAO.js +++ b/guacamole/src/main/webapp/app/rest/services/protocolService.js @@ -21,9 +21,9 @@ */ /** - * The DAO for protocol operations agains the REST API. + * Service for operating on protocol metadata via the REST API. */ -angular.module('rest').factory('protocolDAO', ['$http', function protocolDAO($http) { +angular.module('rest').factory('protocolService', ['$http', function protocolService($http) { var service = {}; diff --git a/guacamole/src/main/webapp/app/rest/services/userDAO.js b/guacamole/src/main/webapp/app/rest/services/userDAO.js deleted file mode 100644 index 2b4b35373..000000000 --- a/guacamole/src/main/webapp/app/rest/services/userDAO.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * The DAO for connection operations agains the REST API. - */ -angular.module('rest').factory('userDAO', ['$http', 'authenticationService', - function userDAO($http, authenticationService) { - - var service = {}; - - /** - * Makes a request to the REST API to get the list of users, - * returning a promise that can be used for processing the results of the call. - * - * @returns {promise} A promise for the HTTP call. - */ - service.getUsers = function getUsers() { - return $http.get("api/user?token=" + authenticationService.getCurrentToken()); - }; - - /** - * Makes a request to the REST API to get the list of users, - * returning a promise that can be used for processing the results of the call. - * - * @param {string} userID The ID of the user to retrieve. - * - * @returns {promise} A promise for the HTTP call. - */ - service.getUser = function getUser(userID) { - return $http.get("api/user/" + userID + "/?token=" + authenticationService.getCurrentToken()); - }; - - /** - * Makes a request to the REST API to delete a user, - * returning a promise that can be used for processing the results of the call. - * - * @param {object} user The user to delete. - * - * @returns {promise} A promise for the HTTP call. - */ - service.deleteUser = function deleteUser(user) { - return $http['delete']( - "api/user/" + user.username + - "?token=" + authenticationService.getCurrentToken()); - }; - - - /** - * Makes a request to the REST API to create a user, - * returning a promise that can be used for processing the results of the call. - * - * @param {object} user The user to create. - * - * @returns {promise} A promise for the HTTP call. - */ - service.createUser = function createUser(user) { - return $http.post( - "api/user/" - + "?token=" + authenticationService.getCurrentToken(), - user - ); - } - - /** - * Makes a request to the REST API to save a user, - * returning a promise that can be used for processing the results of the call. - * - * @param {object} user The user to update. - * - * @returns {promise} A promise for the HTTP call. - */ - service.saveUser = function saveUser(user) { - return $http.post( - "api/user/" + user.username + - "?token=" + authenticationService.getCurrentToken(), - user); - }; - - return service; -}]); diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index afe945dc5..0732ad9a1 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -21,36 +21,79 @@ */ /** - * A service for performing useful user related functionaltiy. + * Service for operating on users via the REST API. */ -angular.module('rest').factory('userService', ['$injector', function userService($injector) { - - var permissionCheckService = $injector.get('permissionCheckService'); +angular.module('rest').factory('userService', ['$http', 'authenticationService', + function userService($http, authenticationService) { var service = {}; /** - * Filters the list of users using the provided permissions. + * Makes a request to the REST API to get the list of users, + * returning a promise that can be used for processing the results of the call. * - * @param {array} users The user list. - * - * @param {object} permissionList The list of permissions to use - * when filtering. - * - * @param {object} permissionCriteria The required permission for each user. - * - * @return {array} The filtered list. + * @returns {promise} A promise for the HTTP call. */ - service.filterUsersByPermission = function filterUsersByPermission(users, permissionList, permissionCriteria) { - for(var i = 0; i < users.length; i++) { - if(!permissionCheckService.checkPermission(permissionList, - "USER", user.username, permissionCriteria)) { - items.splice(i, 1); - continue; - } - } - - return users; + service.getUsers = function getUsers() { + return $http.get("api/user?token=" + authenticationService.getCurrentToken()); + }; + + /** + * Makes a request to the REST API to get the list of users, + * returning a promise that can be used for processing the results of the call. + * + * @param {string} userID The ID of the user to retrieve. + * + * @returns {promise} A promise for the HTTP call. + */ + service.getUser = function getUser(userID) { + return $http.get("api/user/" + userID + "/?token=" + authenticationService.getCurrentToken()); + }; + + /** + * Makes a request to the REST API to delete a user, + * returning a promise that can be used for processing the results of the call. + * + * @param {object} user The user to delete. + * + * @returns {promise} A promise for the HTTP call. + */ + service.deleteUser = function deleteUser(user) { + return $http['delete']( + "api/user/" + user.username + + "?token=" + authenticationService.getCurrentToken()); + }; + + + /** + * Makes a request to the REST API to create a user, + * returning a promise that can be used for processing the results of the call. + * + * @param {object} user The user to create. + * + * @returns {promise} A promise for the HTTP call. + */ + service.createUser = function createUser(user) { + return $http.post( + "api/user/" + + "?token=" + authenticationService.getCurrentToken(), + user + ); + } + + /** + * Makes a request to the REST API to save a user, + * returning a promise that can be used for processing the results of the call. + * + * @param {object} user The user to update. + * + * @returns {promise} A promise for the HTTP call. + */ + service.saveUser = function saveUser(user) { + return $http.post( + "api/user/" + user.username + + "?token=" + authenticationService.getCurrentToken(), + user); }; return service; From 8fa0522ca4299d815402769a833f378fabc735fc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 11 Dec 2014 19:27:37 -0800 Subject: [PATCH 03/37] GUAC-932: Add types for all JSON responses. Ensure default values are documented. --- .../main/webapp/app/rest/types/Connection.js | 2 + .../webapp/app/rest/types/ConnectionGroup.js | 109 +++++++++++++ .../main/webapp/app/rest/types/Permission.js | 153 ++++++++++++++++++ .../main/webapp/app/rest/types/Protocol.js | 69 ++++++++ .../app/rest/types/ProtocolParameter.js | 152 +++++++++++++++++ .../app/rest/types/ProtocolParameterOption.js | 61 +++++++ .../src/main/webapp/app/rest/types/User.js | 63 ++++++++ 7 files changed, 609 insertions(+) create mode 100644 guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js create mode 100644 guacamole/src/main/webapp/app/rest/types/Permission.js create mode 100644 guacamole/src/main/webapp/app/rest/types/Protocol.js create mode 100644 guacamole/src/main/webapp/app/rest/types/ProtocolParameter.js create mode 100644 guacamole/src/main/webapp/app/rest/types/ProtocolParameterOption.js create mode 100644 guacamole/src/main/webapp/app/rest/types/User.js diff --git a/guacamole/src/main/webapp/app/rest/types/Connection.js b/guacamole/src/main/webapp/app/rest/types/Connection.js index ab5aa8eca..a41cba70f 100644 --- a/guacamole/src/main/webapp/app/rest/types/Connection.js +++ b/guacamole/src/main/webapp/app/rest/types/Connection.js @@ -75,6 +75,7 @@ angular.module('rest').factory('Connection', [function defineConnection() { * associated users. * * @type ConnectionHistoryEntry[] + * @default [] */ this.history = template.history || []; @@ -86,6 +87,7 @@ angular.module('rest').factory('Connection', [function defineConnection() { * read and use the connection. * * @type Object. + * @default {} */ this.parameters = template.parameters || {}; diff --git a/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js b/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js new file mode 100644 index 000000000..f2c120c27 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the ConnectionGroup class. + */ +angular.module('rest').factory('ConnectionGroup', [function defineConnectionGroup() { + + /** + * The object returned by REST API calls when representing the data + * associated with a connection group. + * + * @constructor + * @param {ConnectionGroup|Object} [template={}] + * The object whose properties should be copied within the new + * ConnectionGroup. + */ + var ConnectionGroup = function ConnectionGroup(template) { + + // Use empty object by default + template = template || {}; + + /** + * The unique identifier associated with this connection group. + * + * @type String + */ + this.identifier = template.identifier; + + /** + * The unique identifier of the connection group that contains this + * connection group. + * + * @type String + * @default ConnectionGroup.ROOT_IDENTIFIER + */ + this.parentIdentifier = template.parentIdentifier || ConnectionGroup.ROOT_IDENTIFIER; + + /** + * The human-readable name of this connection group, which is not + * necessarily unique. + * + * @type String + */ + this.name = template.name; + + /** + * The type of this connection group, which may be either + * ConnectionGroup.Type.ORGANIZATIONAL or + * ConnectionGroup.Type.BALANCING. + * + * @type String + * @default ConnectionGroup.Type.ORGANIZATIONAL + */ + this.type = template.type || ConnectionGroup.Type.ORGANIZATIONAL; + + }; + + /** + * The reserved identifier which always represents the root connection + * group. + * + * @type String + */ + ConnectionGroup.ROOT_IDENTIFIER = "ROOT"; + + /** + * All valid connection group types. + */ + ConnectionGroup.Type = { + + /** + * The type string associated with balancing connection groups. + * + * @type String + */ + BALANCING : "BALANCING", + + /** + * The type string associated with organizational connection groups. + * + * @type String + */ + ORGANIZATIONAL : "ORGANIZATIONAL" + + }; + + return ConnectionGroup; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/Permission.js b/guacamole/src/main/webapp/app/rest/types/Permission.js new file mode 100644 index 000000000..1fe739056 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/Permission.js @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the Permission class. + */ +angular.module('rest').factory('Permission', [function definePermission() { + + /** + * The object returned by REST API calls when representing the data + * associated with a supported remote desktop protocol. + * + * @constructor + * @param {Permission|Object} [template={}] + * The object whose properties should be copied within the new + * Permission. + */ + var Permission = function Permission(template) { + + // Use empty object by default + template = template || {}; + + /** + * The type of object associated with this permission. + * + * @type String + */ + this.objectType = template.objectType; + + /** + * The identifier of the specific object associated with this + * permission. If the objectType is Permission.ObjectType.SYSTEM, this + * property is not applicable. + * + * @type String + */ + this.objectIdentifier = template.objectIdentifier; + + /** + * The type of this permission, representing the actions granted if + * this permission is present, such as the ability to read or update + * specific objects. Legal values are specified within + * Permission.Type and depend on this permission's objectType. + * + * @type String + */ + this.permissionType = template.permissionType; + + }; + + /** + * Valid object type strings. + */ + Permission.ObjectType = { + + /** + * The permission refers to a specific connection, identified by the + * value of objectIdentifier. + */ + CONNECTION : "CONNECTION", + + /** + * The permission refers to a specific connection group, identified by + * the value of objectIdentifier. + */ + CONNECTION_GROUP : "CONNECTION_GROUP", + + /** + * The permission refers to a specific user, identified by the value of + * objectIdentifier. + */ + USER : "USER", + + /** + * The permission refers to the system as a whole, and the + * objectIdentifier propery is not applicable. + */ + SYSTEM : "SYSTEM" + + }; + + /** + * Valid permission type strings. + */ + Permission.Type = { + + /** + * Permission to read from the specified object. This permission type + * does not apply to SYSTEM permissions. + */ + READ : "READ", + + /** + * Permission to update the specified object. This permission type does + * not apply to SYSTEM permissions. + */ + UPDATE : "UPDATE", + + /** + * Permission to delete the specified object. This permission type does + * not apply to SYSTEM permissions. + */ + DELETE : "DELETE", + + /** + * Permission to administer the specified object or, if the permission + * refers to the system as a whole, permission to administer the entire + * system. + */ + ADMINISTER : "ADMINISTER", + + /** + * Permission to create new users. This permission type may only be + * applied to the system as a whole. + */ + CREATE_USER : "CREATE_USER", + + /** + * Permission to create new connections. This permission type may only + * be applied to the system as a whole. + */ + CREATE_CONNECTION : "CREATE_CONNECTION", + + /** + * Permission to create new connection groups. This permission type may + * only be applied to the system as a whole. + */ + CREATE_CONNECTION_GROUP : "CREATE_CONNECTION_GROUP" + + }; + + return Permission; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/Protocol.js b/guacamole/src/main/webapp/app/rest/types/Protocol.js new file mode 100644 index 000000000..02b6ead27 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/Protocol.js @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the Protocol class. + */ +angular.module('rest').factory('Protocol', [function defineProtocol() { + + /** + * The object returned by REST API calls when representing the data + * associated with a supported remote desktop protocol. + * + * @constructor + * @param {Protocol|Object} [template={}] + * The object whose properties should be copied within the new + * Protocol. + */ + var Protocol = function Protocol(template) { + + // Use empty object by default + template = template || {}; + + /** + * The name which uniquely identifies this protocol. + * + * @type String + */ + this.name = template.name; + + /** + * A human-readable name for this protocol. + * + * @type String + */ + this.title = template.title; + + /** + * An array of all known parameters for this protocol, their types, + * and other information. + * + * @type ProtocolParameter[] + * @default [] + */ + this.parameters = template.parameters || []; + + }; + + return Protocol; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/ProtocolParameter.js b/guacamole/src/main/webapp/app/rest/types/ProtocolParameter.js new file mode 100644 index 000000000..c7ed463aa --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/ProtocolParameter.js @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the ProtocolParameter class. + */ +angular.module('rest').factory('ProtocolParameter', [function defineProtocolParameter() { + + /** + * The object returned by REST API calls when representing the data + * associated with a configuration parameter of a remote desktop protocol. + * + * @constructor + * @param {ProtocolParameter|Object} [template={}] + * The object whose properties should be copied within the new + * ProtocolParameter. + */ + var ProtocolParameter = function ProtocolParameter(template) { + + // Use empty object by default + template = template || {}; + + /** + * The name which uniquely identifies this parameter. + * + * @type String + */ + this.name = template.name; + + /** + * A human-readable name for this parameter. + * + * @type String + */ + this.title = template.title; + + /** + * The type string defining which values this parameter may contain, + * as well as what properties are applicable. Valid types are listed + * within ProtocolParameter.Type. + * + * @type String + * @default ProtocolParameter.Type.TEXT + */ + this.type = template.type || ProtocolParameter.Type.TEXT; + + /** + * The value to set the parameter to, in the case of a BOOLEAN + * parameter, to enable that parameter's effect. + * + * @type String + */ + this.value = template.value; + + /** + * All possible legal values for this parameter. This property is only + * applicable to ENUM type parameters. + * + * @type ProtocolParameterOption[] + */ + this.options = template.options; + + }; + + /** + * All valid protocol parameter types. + */ + ProtocolParameter.Type = { + + /** + * The type string associated with parameters that may contain a single + * line of arbitrary text. + * + * @type String + */ + TEXT : "TEXT", + + /** + * The type string associated with parameters that may contain an + * arbitrary string, where that string represents the username of the + * user authenticating with the remote desktop service. + * + * @type string + */ + USERNAME : "USERNAME", + + /** + * The type string associated with parameters that may contain an + * arbitrary string, where that string represents the password of the + * user authenticating with the remote desktop service. + * + * @type string + */ + PASSWORD : "PASSWORD", + + /** + * The type string associated with parameters that may contain only + * numeric values. + * + * @type string + */ + NUMERIC : "NUMERIC", + + /** + * The type string associated with parameters that may contain only a + * single possible value, where that value enables the parameter's + * effect. + * + * @type String + */ + BOOLEAN : "BOOLEAN", + + /** + * The type string associated with parameters that may contain a + * strictly-defined set of possible values. + * + * @type String + */ + ENUM : "ENUM", + + /** + * The type string associated with parameters that may contain any + * number of lines of arbitrary text. + * + * @type String + */ + MULTILINE : "MULTILINE" + + }; + + return ProtocolParameter; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/ProtocolParameterOption.js b/guacamole/src/main/webapp/app/rest/types/ProtocolParameterOption.js new file mode 100644 index 000000000..09401be48 --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/ProtocolParameterOption.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the ProtocolParameterOption class. + */ +angular.module('rest').factory('ProtocolParameterOption', [function defineProtocolParameterOption() { + + /** + * The object returned by REST API calls when representing a single possible + * legal value of a configuration parameter of a remote desktop protocol. + * + * @constructor + * @param {ProtocolParameterOption|Object} [template={}] + * The object whose properties should be copied within the new + * ProtocolParameterOption. + */ + var ProtocolParameterOption = function ProtocolParameterOption(template) { + + // Use empty object by default + template = template || {}; + + /** + * A human-readable name for this parameter value. + * + * @type String + */ + this.title = template.title; + + /** + * The actual value to set the parameter to, if this option is + * selected. + * + * @type String + */ + this.value = template.value; + + }; + + return ProtocolParameterOption; + +}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/User.js b/guacamole/src/main/webapp/app/rest/types/User.js new file mode 100644 index 000000000..499e4d2ab --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/User.js @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the User class. + */ +angular.module('rest').factory('User', [function defineUser() { + + /** + * The object returned by REST API calls when representing the data + * associated with a user. + * + * @constructor + * @param {User|Object} [template={}] + * The object whose properties should be copied within the new + * User. + */ + var User = function User(template) { + + // Use empty object by default + template = template || {}; + + /** + * The name which uniquely identifies this user. + * + * @type String + */ + this.username = template.username; + + /** + * This user's password. Note that the REST API may not populate this + * property for the sake of security. In most cases, it's not even + * possible for the authentication layer to retrieve the user's true + * password. + * + * @type String + */ + this.password = template.password; + + }; + + return User; + +}]); \ No newline at end of file From 0ef7c4a527a851af0e6aa1ca829ae1519a328f49 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 12 Dec 2014 09:29:39 -0800 Subject: [PATCH 04/37] GUAC-932: Handle FIXMEs for connection/connection group creation. Update connection group service to use ConnectionGroup class. --- .../rest/services/connectionGroupService.js | 95 +++++++++++-------- .../app/rest/services/connectionService.js | 43 +++++---- 2 files changed, 85 insertions(+), 53 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js index 41846cb89..a2f23dfc3 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js @@ -23,30 +23,29 @@ /** * Service for operating on connection groups via the REST API. */ -angular.module('rest').factory('connectionGroupService', ['$http', 'authenticationService', - function connectionGrouService($http, authenticationService) { - - /** - * The ID of the root connection group. - */ - var ROOT_CONNECTION_GROUP_ID = "ROOT"; +angular.module('rest').factory('connectionGroupService', ['$http', 'authenticationService', 'ConnectionGroup', + function connectionGroupService($http, authenticationService, ConnectionGroup) { var service = {}; /** * Makes a request to the REST API to get the list of connection groups, - * returning a promise that can be used for processing the results of the call. + * returning a promise that provides an array of + * @link{ConnectionGroup} objects if successful. * - * @param {string} parentID The parent ID for the connection group. - * If not passed in, it will query a list of the - * connection groups in the root group. + * @param {String} [parentID=ConnectionGroup.ROOT_IDENTIFIER] + * The ID of the connection group whose child connection groups should + * be returned. If not provided, the root connection group will be + * used by default. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise.} + * A promise which will resolve with an array of @link{ConnectionGroup} + * objects upon success. */ service.getConnectionGroups = function getConnectionGroups(parentID) { var parentIDParam = ""; - if(parentID !== undefined) + if (parentID) parentIDParam = "&parentID=" + parentID; return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam); @@ -54,54 +53,74 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati /** * Makes a request to the REST API to get an individual connection group, - * returning a promise that can be used for processing the results of the call. + * returning a promise that provides the corresponding + * @link{ConnectionGroup} if successful. * - * @param {string} connectionGroupID The ID for the connection group. - * If not passed in, it will query the - * root connection group. + * @param {String} [connectionGroupID=ConnectionGroup.ROOT_IDENTIFIER] + * The ID of the connection group to retrieve. If not provided, the + * root connection group will be retrieved by default. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise.} A promise for the HTTP call. + * A promise which will resolve with a @link{ConnectionGroup} upon + * success. */ service.getConnectionGroup = function getConnectionGroup(connectionGroupID) { // Use the root connection group ID if no ID is passed in - connectionGroupID = connectionGroupID || ROOT_CONNECTION_GROUP_ID; + connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken()); }; /** - * Makes a request to the REST API to save a connection group, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to save a connection group, returning a + * promise that can be used for processing the results of the call. If the + * connection group is new, and thus does not yet have an associated + * identifier, the identifier will be automatically set in the provided + * connection group upon success. * - * @param {object} connectionGroup The connection group to update + * @param {ConnectionGroup} connectionGroup The connection group to update. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * save operation is successful. */ service.saveConnectionGroup = function saveConnectionGroup(connectionGroup) { - // This is a new connection group - if(!connectionGroup.identifier) { + + // If connection group is new, add it and set the identifier automatically + if (!connectionGroup.identifier) { return $http.post("api/connectionGroup/?token=" + authenticationService.getCurrentToken(), connectionGroup).success( + + // Set the identifier on the new connection group function setConnectionGroupID(connectionGroupID){ - // Set the identifier on the new connection connectionGroup.identifier = connectionGroupID; - return connectionGroupID; - }); - } else { + } + + ); + } + + // Otherwise, update the existing connection group + else { return $http.post( "api/connectionGroup/" + connectionGroup.identifier + "?token=" + authenticationService.getCurrentToken(), connectionGroup); } + }; /** - * Makes a request to the REST API to move a connection group to a different group, - * returning a promise that can be used for processing the results of the call. + * FIXME: Why is this different from save? * - * @param {object} connectionGroup The connection group to move. + * Makes a request to the REST API to move a connection group to a + * different group, returning a promise that can be used for processing the + * results of the call. + * + * @param {ConnectionGroup} connectionGroup The connection group to move. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * move operation is successful. */ service.moveConnectionGroup = function moveConnectionGroup(connectionGroup) { @@ -113,12 +132,14 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati }; /** - * Makes a request to the REST API to delete a connection group, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to delete a connection group, returning + * a promise that can be used for processing the results of the call. * - * @param {object} connectionGroup The connection group to delete + * @param {ConnectionGroup} connectionGroup The connection group to delete. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * delete operation is successful. */ service.deleteConnectionGroup = function deleteConnectionGroup(connectionGroup) { return $http['delete']( diff --git a/guacamole/src/main/webapp/app/rest/services/connectionService.js b/guacamole/src/main/webapp/app/rest/services/connectionService.js index 1ca391d6b..3c11fb4e4 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionService.js @@ -33,6 +33,7 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer * promise that provides the corresponding @link{Connection} if successful. * * @param {String} id The ID of the connection. + * * @returns {Promise.} * A promise which will resolve with a @link{Connection} upon success. * @@ -48,12 +49,13 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer /** * Makes a request to the REST API to get the list of connections, - * returning a promise that can be used for processing the results of the - * call. + * returning a promise that provides an array of + * @link{Connection} objects if successful. * - * @param {string} parentID The parent ID for the connection. - * If not passed in, it will query a list of the - * connections in the root group. + * @param {String} [parentID=ConnectionGroup.ROOT_IDENTIFIER] + * The ID of the connection group whose child connections should be + * returned. If not provided, the root connection group will be used + * by default. * * @returns {Promise.} * A promise which will resolve with an array of @link{Connection} @@ -62,7 +64,7 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer service.getConnections = function getConnections(parentID) { var parentIDParam = ""; - if(parentID !== undefined) + if (parentID) parentIDParam = "&parentID=" + parentID; return $http.get("api/connection?token=" + authenticationService.getCurrentToken() + parentIDParam); @@ -70,9 +72,12 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer /** * Makes a request to the REST API to save a connection, returning a - * promise that can be used for processing the results of the call. + * promise that can be used for processing the results of the call. If the + * connection is new, and thus does not yet have an associated identifier, + * the identifier will be automatically set in the provided connection + * upon success. * - * @param {Connection} connection The connection to update + * @param {Connection} connection The connection to update. * * @returns {Promise} * A promise for the HTTP call which will succeed if and only if the @@ -89,20 +94,26 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer var connectionToSave = angular.copy(connection); delete connectionToSave.history; - // This is a new connection - if(!connectionToSave.identifier) { + // If connection is new, add it and set the identifier automatically + if (!connectionToSave.identifier) { return $http.post("api/connection/?token=" + authenticationService.getCurrentToken(), connectionToSave).success( + + // Set the identifier on the new connection function setConnectionID(connectionID){ - // Set the identifier on the new connection - connection.identifier = connectionID; // FIXME: Functions with side effects = bad - return connectionID; // FIXME: Why? Where does this value go? - }); - } else { + connection.identifier = connectionID; + } + + ); + } + + // Otherwise, update the existing connection + else { return $http.post( "api/connection/" + connectionToSave.identifier + "?token=" + authenticationService.getCurrentToken(), connectionToSave); } + }; /** @@ -132,7 +143,7 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer * Makes a request to the REST API to delete a connection, * returning a promise that can be used for processing the results of the call. * - * @param {Connection} connection The connection to delete + * @param {Connection} connection The connection to delete. * * @returns {Promise} * A promise for the HTTP call which will succeed if and only if the From a4cc4ab12dfa0bcd283b443135ecb87fec958239 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 12 Dec 2014 09:52:57 -0800 Subject: [PATCH 05/37] GUAC-932: Update permission service to use Permission class. --- .../app/rest/services/permissionService.js | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/services/permissionService.js b/guacamole/src/main/webapp/app/rest/services/permissionService.js index 3ba1b2f9e..7268f9f43 100644 --- a/guacamole/src/main/webapp/app/rest/services/permissionService.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionService.js @@ -29,61 +29,74 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer var service = {}; /** - * Makes a request to the REST API to get the list of permissions for a given user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to get the list of permissions for a + * given user, returning a promise that provides an array of + * @link{Permission} objects if successful. + * - * @param {string} userID The ID of the user to retrieve the permissions for. + * @param {String} userID + * The ID of the user to retrieve the permissions for. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise.} + * A promise which will resolve with an array of @link{Permission} + * objects upon success. */ service.getPermissions = function getPermissions(userID) { return $http.get("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken()); }; /** - * Makes a request to the REST API to add a permission for a given user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to add permissions for a given user, + * returning a promise that can be used for processing the results of the + * call. * - * @param {string} userID The ID of the user to add the permission for. - * @param {object} permission The permission to add. + * @param {String} userID The ID of the user to add the permission for. + * @param {Permission[]} permissions The permissions to add. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * add operation is successful. */ - service.addPermission = function addPermission(userID, permission) { - return $http.post("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission); + service.addPermissions = function addPermissions(userID, permissions) { + return service.patchPermissions(userID, permissions, []); }; - - /** - * Makes a request to the REST API to remove a permission for a given user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to remove permissions for a given user, + * returning a promise that can be used for processing the results of the + * call. * - * @param {string} userID The ID of the user to remove the permission for. - * @param {object} permission The permission to remove. + * @param {String} userID The ID of the user to remove the permission for. + * @param {Permission[]} permissions The permissions to remove. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * remove operation is successful. */ - service.removePermission = function removePermission(userID, permission) { - return $http.post("api/permission/remove/" + userID + "/?token=" + authenticationService.getCurrentToken(), permission); + service.removePermissions = function removePermissions(userID, permissions) { + return service.patchPermissions(userID, [], permissions); }; - /** - * Makes a request to the REST API to modify the permissions for a given user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to modify the permissions for a given + * user, returning a promise that can be used for processing the results of + * the call. * - * @param {string} userID The ID of the user to remove the permission for. - * @param {array} permissionsToAdd The permissions to add. - * @param {array} permissionsToRemove The permissions to remove. + * @param {String} userID The ID of the user to remove the permission for. + * @param {Permission[]} permissionsToAdd The permissions to add. + * @param {Permission[]} permissionsToRemove The permissions to remove. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * patch operation is successful. */ service.patchPermissions = function patchPermissions(userID, permissionsToAdd, permissionsToRemove) { + + var i; var permissionPatch = []; // Add all the add operations to the patch - for(var i = 0; i < permissionsToAdd.length; i++ ) { + for (i = 0; i < permissionsToAdd.length; i++ ) { permissionPatch.push({ op : "add", path : userID, @@ -92,7 +105,7 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer } // Add all the remove operations to the patch - for(var i = 0; i < permissionsToRemove.length; i++ ) { + for (i = 0; i < permissionsToRemove.length; i++ ) { permissionPatch.push({ op : "remove", path : userID, @@ -106,10 +119,9 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer url : "api/permission/?token=" + authenticationService.getCurrentToken(), data : permissionPatch }); - } - - - + + }; return service; + }]); From ed23ae3a2577ff3f2471ff0256ed4ef8b4f2edb5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 12 Dec 2014 09:55:07 -0800 Subject: [PATCH 06/37] GUAC-932: Update protocol service to use Protocol class. Remove single permission add/remove from REST service. --- .../permission/PermissionRESTService.java | 59 ------------------- .../app/rest/services/protocolService.js | 10 +++- 2 files changed, 7 insertions(+), 62 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java index 08e49812b..573763437 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java @@ -101,65 +101,6 @@ public class PermissionRESTService { } - /** - * Adds a permissions for a user with the given userID. - * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param userID The user ID to add the permission for. - * @param permission The permission to add for the user with the given userID. - * @throws GuacamoleException If a problem is encountered while adding the permission. - */ - @POST - @Path("/{userID}") - @AuthProviderRESTExposure - public void addPermission(@QueryParam("token") String authToken, - @PathParam("userID") String userID, APIPermission permission) - throws GuacamoleException { - - UserContext userContext = authenticationService.getUserContext(authToken); - - // Get the user - User user = userContext.getUserDirectory().get(userID); - if (user == null) - throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); - - // Add the new permission - user.addPermission(permission.toPermission()); - userContext.getUserDirectory().update(user); - - } - - /** - * Removes a permissions for a user with the given userID. - * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param userID The user ID to remove the permission for. - * @param permission The permission to remove for the user with the given userID. - * @throws GuacamoleException If a problem is encountered while removing the permission. - */ - @POST - @Path("/remove/{userID}/") - @AuthProviderRESTExposure - public void removePermission(@QueryParam("token") String authToken, - @PathParam("userID") String userID, APIPermission permission) - throws GuacamoleException { - - UserContext userContext = authenticationService.getUserContext(authToken); - - // Get the user - User user = userContext.getUserDirectory().get(userID); - if (user == null) - throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); - - // Remove the permission - user.removePermission(permission.toPermission()); - userContext.getUserDirectory().update(user); - - } - - /** * Applies a given list of permission patches. * diff --git a/guacamole/src/main/webapp/app/rest/services/protocolService.js b/guacamole/src/main/webapp/app/rest/services/protocolService.js index a2a4a5d51..3a3180750 100644 --- a/guacamole/src/main/webapp/app/rest/services/protocolService.js +++ b/guacamole/src/main/webapp/app/rest/services/protocolService.js @@ -28,14 +28,18 @@ angular.module('rest').factory('protocolService', ['$http', function protocolSer var service = {}; /** - * Makes a request to the REST API to get the list of protocols, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to get the list of protocols, returning + * a promise that provides an array of @link{Protocol} objects if + * successful. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise.} + * A promise which will resolve with an array of @link{Protocol} + * objects upon success. */ service.getProtocols = function getProtocols() { return $http.get("api/protocol"); }; return service; + }]); From 97190259c3587b2df1b036d26599cb72dc5936ae Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 12 Dec 2014 10:36:55 -0800 Subject: [PATCH 07/37] GUAC-932: Update user service to use User class. --- .../webapp/app/rest/services/userService.js | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index 0732ad9a1..d5c1431d9 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -30,33 +30,40 @@ angular.module('rest').factory('userService', ['$http', 'authenticationService', /** * Makes a request to the REST API to get the list of users, - * returning a promise that can be used for processing the results of the call. + * returning a promise that provides an array of @link{User} objects if + * successful. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise.} + * A promise which will resolve with an array of @link{User} objects + * upon success. */ service.getUsers = function getUsers() { return $http.get("api/user?token=" + authenticationService.getCurrentToken()); }; /** - * Makes a request to the REST API to get the list of users, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to get the user having the given ID, + * returning a promise that provides the corresponding @link{User} if + * successful. * - * @param {string} userID The ID of the user to retrieve. + * @param {String} userID The ID of the user to retrieve. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise.} + * A promise which will resolve with a @link{User} upon success. */ service.getUser = function getUser(userID) { return $http.get("api/user/" + userID + "/?token=" + authenticationService.getCurrentToken()); }; /** - * Makes a request to the REST API to delete a user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to delete a user, returning a promise + * that can be used for processing the results of the call. * - * @param {object} user The user to delete. + * @param {User} user The user to delete. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * delete operation is successful. */ service.deleteUser = function deleteUser(user) { return $http['delete']( @@ -66,12 +73,14 @@ angular.module('rest').factory('userService', ['$http', 'authenticationService', /** - * Makes a request to the REST API to create a user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to create a user, returning a promise + * that can be used for processing the results of the call. * - * @param {object} user The user to create. + * @param {User} user The user to create. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * create operation is successful. */ service.createUser = function createUser(user) { return $http.post( @@ -79,15 +88,17 @@ angular.module('rest').factory('userService', ['$http', 'authenticationService', + "?token=" + authenticationService.getCurrentToken(), user ); - } + }; /** - * Makes a request to the REST API to save a user, - * returning a promise that can be used for processing the results of the call. + * Makes a request to the REST API to save a user, returning a promise that + * can be used for processing the results of the call. * - * @param {object} user The user to update. + * @param {User} user The user to update. * - * @returns {promise} A promise for the HTTP call. + * @returns {Promise} + * A promise for the HTTP call which will succeed if and only if the + * save operation is successful. */ service.saveUser = function saveUser(user) { return $http.post( @@ -97,4 +108,5 @@ angular.module('rest').factory('userService', ['$http', 'authenticationService', }; return service; + }]); From b1db52541d41d35425b8f306dd3ea5eb086a017a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 12 Dec 2014 15:18:21 -0800 Subject: [PATCH 08/37] GUAC-932: Remove filtering user service. Add permission filtering support to user retrieval endpoint. --- .../guacamole/net/basic/rest/RESTModule.java | 2 - .../permission/PermissionRESTService.java | 1 - .../net/basic/rest/user/UserRESTService.java | 46 +++++++++++---- .../net/basic/rest/user/UserService.java | 59 ------------------- .../manage/controllers/manageController.js | 28 ++++----- .../app/rest/services/legacyUserService.js | 57 ------------------ .../webapp/app/rest/services/userService.js | 29 +++++++-- 7 files changed, 69 insertions(+), 153 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java delete mode 100644 guacamole/src/main/webapp/app/rest/services/legacyUserService.js diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index 55e473755..c9ed5a30a 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -27,7 +27,6 @@ import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupService; import org.glyptodon.guacamole.net.basic.rest.permission.PermissionService; import org.glyptodon.guacamole.net.basic.rest.protocol.ProtocolRetrievalService; -import org.glyptodon.guacamole.net.basic.rest.user.UserService; /** * A Guice Module for setting up dependency injection for the @@ -44,7 +43,6 @@ public class RESTModule extends AbstractModule { bind(ConnectionService.class); bind(ConnectionGroupService.class); bind(PermissionService.class); - bind(UserService.class); bind(ProtocolRetrievalService.class); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java index 573763437..85760f5a4 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import javax.ws.rs.Consumes; import javax.ws.rs.GET; -import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java index 176615b1d..fdc66fd7d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -23,6 +23,7 @@ package org.glyptodon.guacamole.net.basic.rest.user; import com.google.inject.Inject; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.ws.rs.Consumes; @@ -39,6 +40,7 @@ import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.User; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.auth.permission.UserPermission; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; @@ -67,29 +69,47 @@ public class UserRESTService { private AuthenticationService authenticationService; /** - * A service for managing the REST endpoint APIPermission objects. - */ - @Inject - private UserService userService; - - /** - * Gets a list of users in the system. - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. + * Gets a list of users in the system, filtering the returned list by the + * given permission, if specified. + * + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param permission + * If specified, limit the returned list to only those users for whom + * the current user has the given permission. Otherwise, all visible + * users are returned. + * * @return The user list. - * @throws GuacamoleException If a problem is encountered while listing users. + * + * @throws GuacamoleException + * If an error is encountered while retrieving users. */ @GET @AuthProviderRESTExposure - public List getUsers(@QueryParam("token") String authToken) throws GuacamoleException { + public List getUsers(@QueryParam("token") String authToken, + @QueryParam("permission") UserPermission.Type permission) + throws GuacamoleException { UserContext userContext = authenticationService.getUserContext(authToken); + User self = userContext.self(); // Get the directory Directory userDirectory = userContext.getUserDirectory(); - // Convert and return the user directory listing - return userService.convertUserList(userDirectory); + List users = new ArrayList(); + + // Add all users matching the given permission filter + for (String username : userDirectory.getIdentifiers()) { + + if (permission == null || self.hasPermission(new UserPermission(permission, username))) + users.add(new APIUser(userDirectory.get(username))); + + } + + // Return the user directory listing + return users; } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java deleted file mode 100644 index c671b5246..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserService.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.user; - -import java.util.ArrayList; -import java.util.List; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.Directory; -import org.glyptodon.guacamole.net.auth.User; - -/** - * A service for performing useful manipulations on REST Users. - * - * @author James Muehlner - */ -public class UserService { - - /** - * Converts a user directory to a list of APIUser objects for - * exposing with the REST endpoints. - * - * @param userDirectory The user directory to convert for REST endpoint use. - * @return A List of APIUser objects for use with the REST endpoint. - * @throws GuacamoleException If an error occurs while converting the - * user directory. - */ - public List convertUserList(Directory userDirectory) - throws GuacamoleException { - - List restUsers = new ArrayList(); - - for(String username : userDirectory.getIdentifiers()) - restUsers.add(new APIUser(userDirectory.get(username))); - - return restUsers; - - } - -} diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 32b37c4c8..b3a771975 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -25,15 +25,17 @@ */ angular.module('manage').controller('manageController', ['$scope', '$injector', function manageController($scope, $injector) { - - // Get the dependencies commonJS style + + // Required types + var Permission = $injector.get('Permission'); + + // Required services var legacyConnectionGroupService = $injector.get('legacyConnectionGroupService'); var connectionEditModal = $injector.get('connectionEditModal'); var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); var userEditModal = $injector.get('userEditModal'); - var protocolService = $injector.get('protocolService'); - var userService = $injector.get('userService'); - var legacyUserService = $injector.get('legacyUserService'); + var protocolService = $injector.get('protocolService'); + var userService = $injector.get('userService'); // Set status to loading until we have all the connections, groups, and users have loaded $scope.loadingUsers = true; @@ -64,21 +66,13 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', $scope.loadingConnections = false; }); - - userService.getUsers().success(function filterEditableUsers(users) { + + // Retrieve all users for whom we have UPDATE permission + userService.getUsers(Permission.Type.UPDATE).success(function usersReceived(users) { $scope.users = users; - - // Filter the users to only include ones that we have UPDATE for - if(!$scope.currentUserIsAdmin) { - legacyUserService.filterUsersByPermission( - $scope.users, - $scope.currentUserPermissions, - 'UPDATE' - ); - } - $scope.loadingUsers = false; }); + }); /** diff --git a/guacamole/src/main/webapp/app/rest/services/legacyUserService.js b/guacamole/src/main/webapp/app/rest/services/legacyUserService.js deleted file mode 100644 index 894958a2c..000000000 --- a/guacamole/src/main/webapp/app/rest/services/legacyUserService.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A service for performing useful user related functionaltiy. - */ -angular.module('rest').factory('legacyUserService', ['$injector', function legacyUserService($injector) { - - var permissionCheckService = $injector.get('permissionCheckService'); - - var service = {}; - - /** - * Filters the list of users using the provided permissions. - * - * @param {array} users The user list. - * - * @param {object} permissionList The list of permissions to use - * when filtering. - * - * @param {object} permissionCriteria The required permission for each user. - * - * @return {array} The filtered list. - */ - service.filterUsersByPermission = function filterUsersByPermission(users, permissionList, permissionCriteria) { - for(var i = 0; i < users.length; i++) { - if(!permissionCheckService.checkPermission(permissionList, - "USER", user.username, permissionCriteria)) { - items.splice(i, 1); - continue; - } - } - - return users; - }; - - return service; -}]); diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index d5c1431d9..2a42d5a8d 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -33,21 +33,42 @@ angular.module('rest').factory('userService', ['$http', 'authenticationService', * returning a promise that provides an array of @link{User} objects if * successful. * + * @param {String} [permissionType] + * The permission type string of the permission that the current user + * must have for a given user to appear within the list. Valid values + * are listed within Permission.Type. + * * @returns {Promise.} * A promise which will resolve with an array of @link{User} objects * upon success. */ - service.getUsers = function getUsers() { - return $http.get("api/user?token=" + authenticationService.getCurrentToken()); + service.getUsers = function getUsers(permissionType) { + + // Build HTTP parameters set + var httpParameters = { + token : authenticationService.getCurrentToken() + }; + + // Add permission filter if specified + if (permissionType) + httpParameters.permission = permissionType; + + // Retrieve users + return $http({ + method : 'GET', + url : 'api/user', + params : httpParameters + }); + }; - + /** * Makes a request to the REST API to get the user having the given ID, * returning a promise that provides the corresponding @link{User} if * successful. * * @param {String} userID The ID of the user to retrieve. - * + * * @returns {Promise.} * A promise which will resolve with a @link{User} upon success. */ From 68d3b7741e3f4318850cec7e30ce8c47bbdf3033 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 15 Dec 2014 14:19:29 -0800 Subject: [PATCH 09/37] GUAC-932: Migrate to PermissionSet for reading permissions. --- .../guacamole/net/basic/rest/RESTModule.java | 2 - .../net/basic/rest/RESTServletModule.java | 2 - .../basic/rest/permission/APIPermission.java | 228 -------------- .../rest/permission/APIPermissionSet.java | 293 ++++++++++++++++++ .../permission/PermissionRESTService.java | 157 ---------- .../rest/permission/PermissionService.java | 74 ----- .../net/basic/rest/user/UserRESTService.java | 183 +++++++++++ .../app/index/controllers/indexController.js | 24 +- .../manage/controllers/manageController.js | 4 +- .../services/legacyConnectionGroupService.js | 13 +- .../rest/services/permissionCheckService.js | 73 ----- .../app/rest/services/permissionService.js | 22 +- .../webapp/app/rest/services/userService.js | 2 +- .../main/webapp/app/rest/types/Permission.js | 153 --------- .../webapp/app/rest/types/PermissionSet.js | 280 +++++++++++++++++ 15 files changed, 792 insertions(+), 718 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermissionSet.java delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java delete mode 100644 guacamole/src/main/webapp/app/rest/services/permissionCheckService.js delete mode 100644 guacamole/src/main/webapp/app/rest/types/Permission.js create mode 100644 guacamole/src/main/webapp/app/rest/types/PermissionSet.js diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index c9ed5a30a..48ac52b75 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -25,7 +25,6 @@ package org.glyptodon.guacamole.net.basic.rest; import com.google.inject.AbstractModule; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupService; -import org.glyptodon.guacamole.net.basic.rest.permission.PermissionService; import org.glyptodon.guacamole.net.basic.rest.protocol.ProtocolRetrievalService; /** @@ -42,7 +41,6 @@ public class RESTModule extends AbstractModule { // Bind generic low-level services bind(ConnectionService.class); bind(ConnectionGroupService.class); - bind(PermissionService.class); bind(ProtocolRetrievalService.class); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java index 6d8487fdf..cc4e90e6d 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -30,7 +30,6 @@ import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService; import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupRESTService; -import org.glyptodon.guacamole.net.basic.rest.permission.PermissionRESTService; import org.glyptodon.guacamole.net.basic.rest.protocol.ProtocolRESTService; import org.glyptodon.guacamole.net.basic.rest.user.UserRESTService; @@ -48,7 +47,6 @@ public class RESTServletModule extends ServletModule { bind(ClipboardRESTService.class); bind(ConnectionRESTService.class); bind(ConnectionGroupRESTService.class); - bind(PermissionRESTService.class); bind(ProtocolRESTService.class); bind(UserRESTService.class); bind(TokenRESTService.class); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java deleted file mode 100644 index ee97de0fd..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermission.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.permission; - -import org.codehaus.jackson.annotate.JsonIgnoreProperties; -import org.codehaus.jackson.map.annotate.JsonSerialize; -import org.glyptodon.guacamole.net.auth.permission.ConnectionGroupPermission; -import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; -import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; -import org.glyptodon.guacamole.net.auth.permission.Permission; -import org.glyptodon.guacamole.net.auth.permission.SystemPermission; -import org.glyptodon.guacamole.net.auth.permission.UserPermission; - -/** - * A simple user permission to expose through the REST endpoints. - * - * @author James Muehlner - */ -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) -public class APIPermission { - - /** - * Create an empty APIPermission. - */ - public APIPermission() {} - - /** - * The type of object that this permission refers to. - */ - private ObjectType objectType; - - /** - * The type of object that a permission can refer to. - */ - public enum ObjectType { - - /** - * A normal connection. - */ - CONNECTION, - - /** - * A connection group. - */ - CONNECTION_GROUP, - - /** - * A Guacamole user. - */ - USER, - - /** - * The Guacamole system itself. - */ - SYSTEM - - } - - /** - * The identifier of the object that this permission refers to. - */ - private String objectIdentifier; - - /** - * The object permission type for this APIPermission, if relevant. This is - * only used if this.objectType is CONNECTION, CONNECTION_GROUP, or USER. - */ - private ObjectPermission.Type objectPermissionType; - - /** - * The system permission type for this APIPermission, if relevant. This is - * only used if this.objectType is SYSTEM. - */ - private SystemPermission.Type systemPermissionType; - - /** - * Create an APIConnection from a Connection record. - * - * @param permission The permission to create this APIPermission from. - */ - public APIPermission(Permission permission) { - - // Connection permission - if (permission instanceof ConnectionPermission) { - this.objectType = ObjectType.CONNECTION; - this.objectPermissionType = ((ConnectionPermission) permission).getType(); - this.objectIdentifier = ((ConnectionPermission) permission).getObjectIdentifier(); - } - - // Connection group permission - else if (permission instanceof ConnectionGroupPermission) { - this.objectType = ObjectType.CONNECTION_GROUP; - this.objectPermissionType = ((ConnectionGroupPermission) permission).getType(); - this.objectIdentifier = ((ConnectionGroupPermission) permission).getObjectIdentifier(); - } - - // User permission - else if (permission instanceof UserPermission) { - this.objectType = ObjectType.USER; - this.objectPermissionType = ((UserPermission) permission).getType(); - this.objectIdentifier = ((UserPermission) permission).getObjectIdentifier(); - } - - // System permission - else if (permission instanceof SystemPermission) { - this.objectType = ObjectType.SYSTEM; - this.systemPermissionType = ((SystemPermission) permission).getType(); - } - - } - - /** - * Returns the type of object that this permission refers to. - * - * @return The type of object that this permission refers to. - */ - public ObjectType getObjectType() { - return objectType; - } - - /** - * Set the type of object that this permission refers to. - * @param objectType The type of object that this permission refers to. - */ - public void setObjectType(ObjectType objectType) { - this.objectType = objectType; - } - - /** - * Returns a string representation of the permission type. - * - * @return A string representation of the permission type. - */ - public String getPermissionType() { - switch(this.objectType) { - case CONNECTION: - case CONNECTION_GROUP: - case USER: - return this.objectPermissionType.toString(); - case SYSTEM: - return this.systemPermissionType.toString(); - default: - return null; - } - } - - /** - * Set the permission type from a string representation of that type. - * Since it's not clear at this point whether this is an object permission or - * system permission, try to set both of them. - * - * @param permissionType The string representation of the permission type. - */ - public void setPermissionType(String permissionType) { - try { - this.objectPermissionType = ObjectPermission.Type.valueOf(permissionType); - } catch(IllegalArgumentException e) {} - - try { - this.systemPermissionType = SystemPermission.Type.valueOf(permissionType); - } catch(IllegalArgumentException e) {} - } - - /** - * Returns the identifier of the object that this permission refers to. - * - * @return The identifier of the object that this permission refers to. - */ - public String getObjectIdentifier() { - return objectIdentifier; - } - - /** - * Set the identifier of the object that this permission refers to. - * - * @param objectIdentifier The identifier of the object that this permission refers to. - */ - public void setObjectIdentifier(String objectIdentifier) { - this.objectIdentifier = objectIdentifier; - } - - /** - * Returns an org.glyptodon.guacamole.net.auth.permission.Permission - * representation of this APIPermission. - * - * @return An org.glyptodon.guacamole.net.auth.permission.Permission - * representation of this APIPermission. - */ - public Permission toPermission() { - switch(this.objectType) { - case CONNECTION: - return new ConnectionPermission - (this.objectPermissionType, this.objectIdentifier); - case CONNECTION_GROUP: - return new ConnectionGroupPermission - (this.objectPermissionType, this.objectIdentifier); - case USER: - return new UserPermission - (this.objectPermissionType, this.objectIdentifier); - case SYSTEM: - return new SystemPermission(this.systemPermissionType); - default: - return null; - } - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermissionSet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermissionSet.java new file mode 100644 index 000000000..450cbc823 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/APIPermissionSet.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.permission; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleServerException; +import org.glyptodon.guacamole.net.auth.permission.ConnectionGroupPermission; +import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; +import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; +import org.glyptodon.guacamole.net.auth.permission.Permission; +import org.glyptodon.guacamole.net.auth.permission.SystemPermission; +import org.glyptodon.guacamole.net.auth.permission.UserPermission; + +/** + * The set of permissions which are granted to a specific user, organized by + * object type and, if applicable, identifier. This object can be constructed + * with arbitrary permissions present, or manipulated after creation through + * the manipulation or replacement of its collections of permissions, but is + * otherwise not intended for internal use as a data structure for permissions. + * Its primary purpose is as a hierarchical format for exchanging granted + * permissions with REST clients. + */ +public class APIPermissionSet { + + /** + * Map of connection ID to the set of granted permissions. + */ + private Map> connectionPermissions = new HashMap>(); + + /** + * Map of connection group ID to the set of granted permissions. + */ + private Map> connectionGroupPermissions = new HashMap>(); + + /** + * Map of user ID to the set of granted permissions. + */ + private Map> userPermissions = new HashMap>(); + + /** + * Set of all granted system-level permissions. + */ + private EnumSet systemPermissions = EnumSet.noneOf(SystemPermission.Type.class); + + /** + * Adds the given object permission to the given map of object identifier + * to permission set. + * + * @param permissions + * The map to add the given permission to. + * + * @param permission + * The permission to add. + */ + private void addPermission(Map> permissions, ObjectPermission permission) { + + // Pull set of permissions for given object + String id = permission.getObjectIdentifier(); + EnumSet types = permissions.get(id); + + // If set does not yet exist, create it + if (types == null) { + types = EnumSet.of(permission.getType()); + permissions.put(id, types); + } + + // Otherwise, add the specified permission + else + types.add(permission.getType()); + + } + + /** + * Adds the given system-level permission to the given set of granted + * system permissions. + * + * @param permissions + * The set of system permissions to add the given permission to. + * + * @param permission + * The permission to add. + */ + private void addPermission(EnumSet permissions, SystemPermission permission) { + permissions.add(permission.getType()); + } + + /** + * Adds the given permission to the appropriate type-specific set or map of + * permissions based on the permission class. Only connection, connection + * group, user, and system permissions are supported. Unsupported + * permission types will result in a GuacamoleException being thrown. + * + * @param permission The permission to add. + * @throws GuacamoleException If the permission is of an unsupported type. + */ + private void addPermission(Permission permission) throws GuacamoleException { + + // Connection permissions + if (permission instanceof ConnectionPermission) + addPermission(connectionPermissions, (ConnectionPermission) permission); + + // Connection group permissions + else if (permission instanceof ConnectionGroupPermission) + addPermission(connectionGroupPermissions, (ConnectionGroupPermission) permission); + + // User permissions + else if (permission instanceof UserPermission) + addPermission(userPermissions, (UserPermission) permission); + + // System permissions + else if (permission instanceof SystemPermission) + addPermission(systemPermissions, (SystemPermission) permission); + + // Unknown / unsupported permission type + else + throw new GuacamoleServerException("Serialization of permission type \"" + permission.getClass() + "\" not implemented."); + + } + + /** + * Creates a new permission set which contains no granted permissions. Any + * permissions must be added by manipulating or replacing the applicable + * permission collection. + */ + public APIPermissionSet() { + } + + /** + * Creates a new permission set having the given permissions. + * + * @param permissions + * The permissions to initially store within the permission set. + * + * @throws GuacamoleException + * If any of the given permissions are of an unsupported type. + */ + public APIPermissionSet(Iterable permissions) throws GuacamoleException { + + // Add all provided permissions + for (Permission permission : permissions) + addPermission(permission); + + } + + /** + * Creates a new permission set having the given permissions. + * + * @param permissions + * The permissions to initially store within the permission set. + * + * @throws GuacamoleException + * If any of the given permissions are of an unsupported type. + */ + public APIPermissionSet(Permission... permissions) throws GuacamoleException { + + // Add all provided permissions + for (Permission permission : permissions) + addPermission(permission); + + } + + /** + * Returns a map of connection IDs to the set of permissions granted for + * that connection. If no permissions are granted to a particular + * connection, its ID will not be present as a key in the map. This map is + * mutable, and changes to this map will affect the permission set + * directly. + * + * @return + * A map of connection IDs to the set of permissions granted for that + * connection. + */ + public Map> getConnectionPermissions() { + return connectionPermissions; + } + + /** + * Returns a map of connection group IDs to the set of permissions granted + * for that connection group. If no permissions are granted to a particular + * connection group, its ID will not be present as a key in the map. This + * map is mutable, and changes to this map will affect the permission set + * directly. + * + * @return + * A map of connection group IDs to the set of permissions granted for + * that connection group. + */ + public Map> getConnectionGroupPermissions() { + return connectionGroupPermissions; + } + + /** + * Returns a map of user IDs to the set of permissions granted for that + * user. If no permissions are granted to a particular user, its ID will + * not be present as a key in the map. This map is mutable, and changes to + * to this map will affect the permission set directly. + * + * @return + * A map of user IDs to the set of permissions granted for that user. + */ + public Map> getUserPermissions() { + return userPermissions; + } + + /** + * Returns the set of granted system-level permissions. If no permissions + * are granted at the system level, this will be an empty set. This set is + * mutable, and changes to this set will affect the permission set + * directly. + * + * @return + * The set of granted system-level permissions. + */ + public EnumSet getSystemPermissions() { + return systemPermissions; + } + + /** + * Replaces the current map of connection permissions with the given map, + * which must map connection ID to its corresponding set of granted + * permissions. If a connection has no permissions, its ID must not be + * present as a key in the map. + * + * @param connectionPermissions + * The map which must replace the currently-stored map of permissions. + */ + public void setConnectionPermissions(Map> connectionPermissions) { + this.connectionPermissions = connectionPermissions; + } + + /** + * Replaces the current map of connection group permissions with the given + * map, which must map connection group ID to its corresponding set of + * granted permissions. If a connection group has no permissions, its ID + * must not be present as a key in the map. + * + * @param connectionGroupPermissions + * The map which must replace the currently-stored map of permissions. + */ + public void setConnectionGroupPermissions(Map> connectionGroupPermissions) { + this.connectionGroupPermissions = connectionGroupPermissions; + } + + /** + * Replaces the current map of user permissions with the given map, which + * must map user ID to its corresponding set of granted permissions. If a + * user has no permissions, its ID must not be present as a key in the map. + * + * @param userPermissions + * The map which must replace the currently-stored map of permissions. + */ + public void setUserPermissions(Map> userPermissions) { + this.userPermissions = userPermissions; + } + + /** + * Replaces the current set of system-level permissions with the given set. + * If no system-level permissions are granted, the empty set must be + * specified. + * + * @param systemPermissions + * The set which must replace the currently-stored set of permissions. + */ + public void setSystemPermissions(EnumSet systemPermissions) { + this.systemPermissions = systemPermissions; + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java deleted file mode 100644 index 85760f5a4..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionRESTService.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.permission; - -import com.google.inject.Inject; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.ws.rs.Consumes; -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 javax.ws.rs.core.Response.Status; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.Directory; -import org.glyptodon.guacamole.net.auth.User; -import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.auth.permission.Permission; -import org.glyptodon.guacamole.net.basic.rest.APIPatch; -import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; -import org.glyptodon.guacamole.net.basic.rest.HTTPException; -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; - -/** - * A REST Service for handling connection CRUD operations. - * - * @author James Muehlner - */ -@Path("/permission") -@Produces(MediaType.APPLICATION_JSON) -@Consumes(MediaType.APPLICATION_JSON) -public class PermissionRESTService { - - /** - * Logger for this class. - */ - private static final Logger logger = LoggerFactory.getLogger(PermissionRESTService.class); - - /** - * A service for authenticating users from auth tokens. - */ - @Inject - private AuthenticationService authenticationService; - - /** - * A service for managing the REST endpoint APIPermission objects. - */ - @Inject - private PermissionService permissionService; - - /** - * Gets a list of permissions for the user with the given userID. - * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param userID The ID of the user to retrieve permissions for. - * @return The permission list. - * @throws GuacamoleException If a problem is encountered while listing permissions. - */ - @GET - @Path("/{userID}") - @AuthProviderRESTExposure - public List getPermissions(@QueryParam("token") String authToken, @PathParam("userID") String userID) - throws GuacamoleException { - - UserContext userContext = authenticationService.getUserContext(authToken); - - // Get the user - User user = userContext.getUserDirectory().get(userID); - if (user == null) - throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); - - return permissionService.convertPermissionList(user.getPermissions()); - - } - - /** - * Applies a given list of permission patches. - * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param patches The permission patches to apply for this request. - * @throws GuacamoleException If a problem is encountered while removing the permission. - */ - @PATCH - @AuthProviderRESTExposure - public void patchPermissions(@QueryParam("token") String authToken, - List> patches) throws GuacamoleException { - - UserContext userContext = authenticationService.getUserContext(authToken); - - // Get the user directory - Directory userDirectory = userContext.getUserDirectory(); - - // All users who have had permissions added or removed - Map modifiedUsers = new HashMap(); - - for (APIPatch patch : patches) { - - String userID = patch.getPath(); - Permission permission = patch.getValue().toPermission(); - - // See if we've already modified this user in this request - User user = modifiedUsers.get(userID); - if (user == null) - user = userDirectory.get(userID); - - if (user == null) - throw new HTTPException(Status.NOT_FOUND, "User not found with userID " + userID + "."); - - // Only the add and remove operations are supported for permissions - switch(patch.getOp()) { - case add: - user.addPermission(permission); - modifiedUsers.put(userID, user); - break; - case remove: - user.removePermission(permission); - modifiedUsers.put(userID, user); - break; - } - - } - - // Save the permission changes for all modified users - for (User user : modifiedUsers.values()) - userDirectory.update(user); - - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java deleted file mode 100644 index bdff3d84b..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/permission/PermissionService.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.permission; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import org.glyptodon.guacamole.net.auth.permission.Permission; - -/** - * A service for performing useful manipulations on REST Permissions. - * - * @author James Muehlner - */ -public class PermissionService { - - /** - * Converts a list of Permission to a list of APIPermission objects for - * exposing with the REST endpoints. - * - * @param permissions The Connections to convert for REST endpoint use. - * @return A List of APIPermission objects for use with the REST endpoint. - */ - public List convertPermissionList(Iterable permissions) { - - List restPermissions = new ArrayList(); - - for(Permission permission : permissions) - restPermissions.add(new APIPermission(permission)); - - return restPermissions; - - } - - /** - * Converts a list of APIPermission to a set of Permission objects for internal - * Guacamole use. - * - * @param restPermissions The APIPermission objects from the REST endpoints. - * @return a List of Permission objects for internal Guacamole use. - */ - public Set convertAPIPermissionList(Iterable restPermissions) { - - Set permissions = new HashSet(); - - for(APIPermission restPermission : restPermissions) - permissions.add(restPermission.toPermission()); - - return permissions; - - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java index fdc66fd7d..5927b1b35 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -36,14 +36,25 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.User; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.auth.permission.ConnectionGroupPermission; +import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; +import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; +import org.glyptodon.guacamole.net.auth.permission.Permission; +import org.glyptodon.guacamole.net.auth.permission.SystemPermission; import org.glyptodon.guacamole.net.auth.permission.UserPermission; +import org.glyptodon.guacamole.net.basic.rest.APIPatch; +import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.add; +import static org.glyptodon.guacamole.net.basic.rest.APIPatch.Operation.remove; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; +import org.glyptodon.guacamole.net.basic.rest.PATCH; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.glyptodon.guacamole.net.basic.rest.permission.APIPermissionSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +72,30 @@ public class UserRESTService { * Logger for this class. */ private static final Logger logger = LoggerFactory.getLogger(UserRESTService.class); + + /** + * The prefix of any path within an operation of a JSON patch which + * modifies the permissions of a user regarding a specific connection. + */ + private static final String CONNECTION_PERMISSION_PATCH_PATH_PREFIX = "/connectionPermissions/"; + + /** + * The prefix of any path within an operation of a JSON patch which + * modifies the permissions of a user regarding a specific connection group. + */ + private static final String CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX = "/connectionGroupPermissions/"; + + /** + * The prefix of any path within an operation of a JSON patch which + * modifies the permissions of a user regarding another, specific user. + */ + private static final String USER_PERMISSION_PATCH_PATH_PREFIX = "/userPermissions/"; + + /** + * The path of any operation within a JSON patch which modifies the + * permissions of a user regarding the entire system. + */ + private static final String SYSTEM_PERMISSION_PATCH_PATH = "/systemPermissions"; /** * A service for authenticating users from auth tokens. @@ -239,4 +274,152 @@ public class UserRESTService { } + /** + * Gets a list of permissions for the user with the given userID. + * + * @param authToken The authentication token that is used to authenticate + * the user performing the operation. + * @param userID The ID of the user to retrieve permissions for. + * @return The permission list. + * @throws GuacamoleException If a problem is encountered while listing permissions. + */ + @GET + @Path("/{userID}/permissions") + @AuthProviderRESTExposure + public APIPermissionSet getPermissions(@QueryParam("token") String authToken, @PathParam("userID") String userID) + throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the user + User user = userContext.getUserDirectory().get(userID); + if (user == null) + throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); + + return new APIPermissionSet(user.getPermissions()); + + } + + /** + * Applies a given list of permission patches. Each patch specifies either + * an "add" or a "remove" operation for a permission type, represented by + * a string. Valid permission types depend on the path of each patch + * operation, as the path dictates the permission being modified, such as + * "/connectionPermissions/42" or "/systemPermissions". + * + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param userID + * The ID of the user to modify the permissions of. + * + * @param patches + * The permission patches to apply for this request. + * + * @throws GuacamoleException + * If a problem is encountered while modifying permissions. + */ + @PATCH + @Path("/{userID}/permissions") + @AuthProviderRESTExposure + public void patchPermissions(@QueryParam("token") String authToken, + @PathParam("userID") String userID, + List> patches) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the user directory + Directory userDirectory = userContext.getUserDirectory(); + + // Get the user + User user = userContext.getUserDirectory().get(userID); + if (user == null) + throw new HTTPException(Status.NOT_FOUND, "User not found with the provided userID."); + + // Apply all patch operations individually + for (APIPatch patch : patches) { + + Permission permission; + + String path = patch.getPath(); + + // Create connection permission if path has connection prefix + if (path.startsWith(CONNECTION_PERMISSION_PATCH_PATH_PREFIX)) { + + // Get identifier and type from patch operation + String identifier = path.substring(CONNECTION_PERMISSION_PATCH_PATH_PREFIX.length()); + ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue()); + + // Create corresponding permission + permission = new ConnectionPermission(type, identifier); + + } + + // Create connection group permission if path has connection group prefix + else if (path.startsWith(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX)) { + + // Get identifier and type from patch operation + String identifier = path.substring(CONNECTION_GROUP_PERMISSION_PATCH_PATH_PREFIX.length()); + ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue()); + + // Create corresponding permission + permission = new ConnectionGroupPermission(type, identifier); + + } + + // Create user permission if path has user prefix + else if (path.startsWith(USER_PERMISSION_PATCH_PATH_PREFIX)) { + + // Get identifier and type from patch operation + String identifier = path.substring(USER_PERMISSION_PATCH_PATH_PREFIX.length()); + ObjectPermission.Type type = ObjectPermission.Type.valueOf(patch.getValue()); + + // Create corresponding permission + permission = new UserPermission(type, identifier); + + } + + // Create system permission if path is system path + else if (path.startsWith(SYSTEM_PERMISSION_PATCH_PATH)) { + + // Get identifier and type from patch operation + SystemPermission.Type type = SystemPermission.Type.valueOf(patch.getValue()); + + // Create corresponding permission + permission = new SystemPermission(type); + + } + + // Otherwise, the path is not supported + else + throw new HTTPException(Status.BAD_REQUEST, "Unsupported patch path: \"" + path + "\""); + + // Add or remove permission based on operation + switch (patch.getOp()) { + + // Add permission + case add: + user.addPermission(permission); + break; + + // Remove permission + case remove: + user.removePermission(permission); + break; + + // Unsupported patch operation + default: + throw new HTTPException(Status.BAD_REQUEST, "Unsupported patch operation: \"" + patch.getOp() + "\""); + + } + + } // end for each patch operation + + // Save the permission changes + userDirectory.update(user); + + } + + } diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index cc256fc38..25ca651b6 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -25,10 +25,12 @@ */ angular.module('index').controller('indexController', ['$scope', '$injector', function indexController($scope, $injector) { - - // Get the dependencies commonJS style - var permissionService = $injector.get("permissionService"), - permissionCheckService = $injector.get("permissionCheckService"), + + // Get class dependencies + var PermissionSet = $injector.get("PermissionSet"); + + // Get services + var permissionService = $injector.get("permissionService"), authenticationService = $injector.get("authenticationService"), $q = $injector.get("$q"), $document = $injector.get("$document"), @@ -169,13 +171,15 @@ angular.module('index').controller('indexController', ['$scope', '$injector', permissionService.getPermissions($scope.currentUserID).success(function fetchCurrentUserPermissions(permissions) { $scope.currentUserPermissions = permissions; - // Will be true if the user is an admin - $scope.currentUserIsAdmin = permissionCheckService.checkPermission($scope.currentUserPermissions, "SYSTEM", undefined, "ADMINISTER"); + // Whether the user has system-wide admin permission + $scope.currentUserIsAdmin = PermissionSet.hasSystemPermission($scope.currentUserPermissions, PermissionSet.SystemPermissionType.ADMINISTER); + + // Whether the user can update at least one object + $scope.currentUserHasUpdate = $scope.currentUserIsAdmin + || PermissionSet.hasConnectionPermission($scope.currentUserPermissions, "UPDATE") + || PermissionSet.hasConnectionGroupPermission($scope.currentUserPermissions, "UPDATE") + || PermissionSet.hasUserPermission($scope.currentUserPermissions, "UPDATE"); - // Will be true if the user is an admin or has update access to any object - $scope.currentUserHasUpdate = $scope.currentUserIsAdmin || - permissionCheckService.checkPermission($scope.currentUserPermissions, undefined, undefined, "UPDATE"); - permissionsLoaded.resolve(); }); }; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index b3a771975..77ce0c77f 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -27,7 +27,7 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', function manageController($scope, $injector) { // Required types - var Permission = $injector.get('Permission'); + var PermissionSet = $injector.get('PermissionSet'); // Required services var legacyConnectionGroupService = $injector.get('legacyConnectionGroupService'); @@ -68,7 +68,7 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', }); // Retrieve all users for whom we have UPDATE permission - userService.getUsers(Permission.Type.UPDATE).success(function usersReceived(users) { + userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE).success(function usersReceived(users) { $scope.users = users; $scope.loadingUsers = false; }); diff --git a/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js index a9e4e71b3..e8eb9748b 100644 --- a/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js @@ -25,9 +25,12 @@ */ angular.module('rest').factory('legacyConnectionGroupService', ['$injector', function legacyConnectionGroupService($injector) { - var connectionGroupService = $injector.get('connectionGroupService'); + // Get class dependencies + var PermissionSet = $injector.get("PermissionSet"); + + // Get services + var connectionGroupService = $injector.get('connectionGroupService'); var connectionService = $injector.get('connectionService'); - var permissionCheckService = $injector.get('permissionCheckService'); var $q = $injector.get('$q'); var displayObjectPreparationService = $injector.get('displayObjectPreparationService'); @@ -196,8 +199,7 @@ angular.module('rest').factory('legacyConnectionGroupService', ['$injector', fun * item, check now to see if the permission exists. If not, * remove the item. */ - if(!permissionCheckService.checkPermission(permissionList, - "CONNECTION", item.identifier, requiredConnectionPermission)) { + if(!PermissionSet.hasConnectionPermission(permissionList, item.identifier, requiredConnectionPermission)) { items.splice(i, 1); continue; } @@ -210,8 +212,7 @@ angular.module('rest').factory('legacyConnectionGroupService', ['$injector', fun * remove the item. */ if(requiredConnectionGroupPermission) { - if(!permissionCheckService.checkPermission(permissionList, - "CONNECTION_GROUP", item.identifier, requiredConnectionGroupPermission)) { + if(!PermissionSet.hasConnectionGroupPermission(permissionList, item.identifier, requiredConnectionGroupPermission)) { items.splice(i, 1); continue; } diff --git a/guacamole/src/main/webapp/app/rest/services/permissionCheckService.js b/guacamole/src/main/webapp/app/rest/services/permissionCheckService.js deleted file mode 100644 index 80ee4cd39..000000000 --- a/guacamole/src/main/webapp/app/rest/services/permissionCheckService.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A service for checking if a specific permission exists - * in a given list of permissions. - */ -angular.module('rest').factory('permissionCheckService', [ - function permissionCheckService() { - - var service = {}; - - /** - * A service for checking if the given permission list contains the given - * permission, defined by the objectType, objectID, and permissionType. - * If the objectType or objectID are not passed, they will not be checked. - * - * For example, checkPermission(list, "CONNECTION", undefined, "READ") would - * check if the permission list contains permission to read any connection. - * - * @param {array} permissions The array of permissions to check. - * @param {string} objectType The object type for the permission. - * If not passed, this will not be checked. - * @param {string} objectID The ID of the object the permission is for. - * If not passed, this will not be checked. - * @param {string} permissionType The actual permission type to check for. - * @returns {boolean} True if the given permissions contain the requested permission, false otherwise. - */ - service.checkPermission = function checkPermission(permissions, objectType, objectID, permissionType) { - - // Loop through all the permissions and check if any of them match the given parameters - for(var i = 0; i < permissions.length; i++) { - var permission = permissions[i]; - - if(objectType === "SYSTEM") { - // System permissions have no object ID, we only need to check the type. - if(permission.permissionType === permissionType) - return true; - } - else { - // Object permissions need to match the object ID and type if given. - if(permission.permissionType === permissionType && - (!objectType || permission.objectType === objectType) && - (!objectID || permission.objectID === objectID)) - return true; - } - } - - // Didn't find any that matched - return false; - } - - return service; -}]); diff --git a/guacamole/src/main/webapp/app/rest/services/permissionService.js b/guacamole/src/main/webapp/app/rest/services/permissionService.js index 7268f9f43..88e42a421 100644 --- a/guacamole/src/main/webapp/app/rest/services/permissionService.js +++ b/guacamole/src/main/webapp/app/rest/services/permissionService.js @@ -37,12 +37,12 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer * @param {String} userID * The ID of the user to retrieve the permissions for. * - * @returns {Promise.} - * A promise which will resolve with an array of @link{Permission} - * objects upon success. + * @returns {Promise.} + * A promise which will resolve with a @link{PermissionSet} upon + * success. */ service.getPermissions = function getPermissions(userID) { - return $http.get("api/permission/" + userID + "/?token=" + authenticationService.getCurrentToken()); + return $http.get("api/user/" + userID + "/permissions?token=" + authenticationService.getCurrentToken()); }; /** @@ -51,14 +51,14 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer * call. * * @param {String} userID The ID of the user to add the permission for. - * @param {Permission[]} permissions The permissions to add. + * @param {PermissionSet} permissions The permissions to add. * * @returns {Promise} * A promise for the HTTP call which will succeed if and only if the * add operation is successful. */ service.addPermissions = function addPermissions(userID, permissions) { - return service.patchPermissions(userID, permissions, []); + return service.patchPermissions(userID, permissions, null); }; /** @@ -67,14 +67,14 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer * call. * * @param {String} userID The ID of the user to remove the permission for. - * @param {Permission[]} permissions The permissions to remove. + * @param {PermissionSet} permissions The permissions to remove. * * @returns {Promise} * A promise for the HTTP call which will succeed if and only if the * remove operation is successful. */ service.removePermissions = function removePermissions(userID, permissions) { - return service.patchPermissions(userID, [], permissions); + return service.patchPermissions(userID, null, permissions); }; /** @@ -83,8 +83,8 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer * the call. * * @param {String} userID The ID of the user to remove the permission for. - * @param {Permission[]} permissionsToAdd The permissions to add. - * @param {Permission[]} permissionsToRemove The permissions to remove. + * @param {PermissionSet} [permissionsToAdd] The permissions to add. + * @param {PermissionSet} [permissionsToRemove] The permissions to remove. * * @returns {Promise} * A promise for the HTTP call which will succeed if and only if the @@ -92,6 +92,8 @@ angular.module('rest').factory('permissionService', ['$http', 'authenticationSer */ service.patchPermissions = function patchPermissions(userID, permissionsToAdd, permissionsToRemove) { + // FIXME: This will NOT work, now that PermissionSet is used + var i; var permissionPatch = []; diff --git a/guacamole/src/main/webapp/app/rest/services/userService.js b/guacamole/src/main/webapp/app/rest/services/userService.js index 2a42d5a8d..72b70458c 100644 --- a/guacamole/src/main/webapp/app/rest/services/userService.js +++ b/guacamole/src/main/webapp/app/rest/services/userService.js @@ -36,7 +36,7 @@ angular.module('rest').factory('userService', ['$http', 'authenticationService', * @param {String} [permissionType] * The permission type string of the permission that the current user * must have for a given user to appear within the list. Valid values - * are listed within Permission.Type. + * are listed within PermissionSet.ObjectType. * * @returns {Promise.} * A promise which will resolve with an array of @link{User} objects diff --git a/guacamole/src/main/webapp/app/rest/types/Permission.js b/guacamole/src/main/webapp/app/rest/types/Permission.js deleted file mode 100644 index 1fe739056..000000000 --- a/guacamole/src/main/webapp/app/rest/types/Permission.js +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Service which defines the Permission class. - */ -angular.module('rest').factory('Permission', [function definePermission() { - - /** - * The object returned by REST API calls when representing the data - * associated with a supported remote desktop protocol. - * - * @constructor - * @param {Permission|Object} [template={}] - * The object whose properties should be copied within the new - * Permission. - */ - var Permission = function Permission(template) { - - // Use empty object by default - template = template || {}; - - /** - * The type of object associated with this permission. - * - * @type String - */ - this.objectType = template.objectType; - - /** - * The identifier of the specific object associated with this - * permission. If the objectType is Permission.ObjectType.SYSTEM, this - * property is not applicable. - * - * @type String - */ - this.objectIdentifier = template.objectIdentifier; - - /** - * The type of this permission, representing the actions granted if - * this permission is present, such as the ability to read or update - * specific objects. Legal values are specified within - * Permission.Type and depend on this permission's objectType. - * - * @type String - */ - this.permissionType = template.permissionType; - - }; - - /** - * Valid object type strings. - */ - Permission.ObjectType = { - - /** - * The permission refers to a specific connection, identified by the - * value of objectIdentifier. - */ - CONNECTION : "CONNECTION", - - /** - * The permission refers to a specific connection group, identified by - * the value of objectIdentifier. - */ - CONNECTION_GROUP : "CONNECTION_GROUP", - - /** - * The permission refers to a specific user, identified by the value of - * objectIdentifier. - */ - USER : "USER", - - /** - * The permission refers to the system as a whole, and the - * objectIdentifier propery is not applicable. - */ - SYSTEM : "SYSTEM" - - }; - - /** - * Valid permission type strings. - */ - Permission.Type = { - - /** - * Permission to read from the specified object. This permission type - * does not apply to SYSTEM permissions. - */ - READ : "READ", - - /** - * Permission to update the specified object. This permission type does - * not apply to SYSTEM permissions. - */ - UPDATE : "UPDATE", - - /** - * Permission to delete the specified object. This permission type does - * not apply to SYSTEM permissions. - */ - DELETE : "DELETE", - - /** - * Permission to administer the specified object or, if the permission - * refers to the system as a whole, permission to administer the entire - * system. - */ - ADMINISTER : "ADMINISTER", - - /** - * Permission to create new users. This permission type may only be - * applied to the system as a whole. - */ - CREATE_USER : "CREATE_USER", - - /** - * Permission to create new connections. This permission type may only - * be applied to the system as a whole. - */ - CREATE_CONNECTION : "CREATE_CONNECTION", - - /** - * Permission to create new connection groups. This permission type may - * only be applied to the system as a whole. - */ - CREATE_CONNECTION_GROUP : "CREATE_CONNECTION_GROUP" - - }; - - return Permission; - -}]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js new file mode 100644 index 000000000..ca8e2f30f --- /dev/null +++ b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Service which defines the PermissionSet class. + */ +angular.module('rest').factory('PermissionSet', [function definePermissionSet() { + + /** + * The object returned by REST API calls when representing the permissions + * granted to a specific user. + * + * @constructor + * @param {PermissionSet|Object} [template={}] + * The object whose properties should be copied within the new + * PermissionSet. + */ + var PermissionSet = function Permission(template) { + + // Use empty object by default + template = template || {}; + + /** + * Map of connection identifiers to the corresponding array of granted + * permissions. Each permission is represented by a string listed + * within PermissionSet.ObjectPermissionType. + * + * @type Object. + */ + this.connectionPermissions = template.connectionPermissions || {}; + + /** + * Map of connection group identifiers to the corresponding array of + * granted permissions. Each permission is represented by a string + * listed within PermissionSet.ObjectPermissionType. + * + * @type Object. + */ + this.connectionGroupPermissions = template.connectionGroupPermissions || {}; + + /** + * Map of user identifiers to the corresponding array of granted + * permissions. Each permission is represented by a string listed + * within PermissionSet.ObjectPermissionType. + * + * @type Object. + */ + this.userPermissions = template.userPermissions || {}; + + /** + * Array of granted system permissions. Each permission is represented + * by a string listed within PermissionSet.SystemPermissionType. + * + * @type String[] + */ + this.systemPermissions = template.systemPermissions || []; + + }; + + /** + * Valid object permission type strings. + */ + PermissionSet.ObjectPermissionType = { + + /** + * Permission to read from the specified object. + */ + READ : "READ", + + /** + * Permission to update the specified object. + */ + UPDATE : "UPDATE", + + /** + * Permission to delete the specified object. + */ + DELETE : "DELETE", + + /** + * Permission to administer the specified object + */ + ADMINISTER : "ADMINISTER" + + }; + + /** + * Valid system permission type strings. + */ + PermissionSet.SystemPermissionType = { + + /** + * Permission to administer the entire system. + */ + ADMINISTER : "ADMINISTER", + + /** + * Permission to create new users. + */ + CREATE_USER : "CREATE_USER", + + /** + * Permission to create new connections. + */ + CREATE_CONNECTION : "CREATE_CONNECTION", + + /** + * Permission to create new connection groups. + */ + CREATE_CONNECTION_GROUP : "CREATE_CONNECTION_GROUP" + + }; + + /** + * Returns whether the given permission is granted for at least one + * arbitrary object, regardless of ID. + * + * @param {Object.} permMap + * The permission map to check, where each entry maps an object + * identifer to the array of granted permissions. + * + * @param {String} type + * The permission to search for, as defined by + * PermissionSet.ObjectPermissionType. + * + * @returns {Boolean} + * true if the permission is present (granted), false otherwise. + */ + var containsPermission = function containsPermission(permMap, type) { + + // Search all identifiers for given permission + for (var identifier in permMap) { + + // If permission is granted, then no further searching is necessary + if (permMap[identifier].indexOf(type) !== -1) + return true; + + } + + // No such permission exists + return false; + + }; + + /** + * Returns whether the given permission is granted for the arbitrary + * object having the given ID. If no ID is given, this function determines + * whether the permission is granted at all for any such arbitrary object. + * + * @param {Object.} permMap + * The permission map to check, where each entry maps an object + * identifer to the array of granted permissions. + * + * @param {String} type + * The permission to search for, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} [identifier] + * The identifier of the object to which the permission applies. + * + * @returns {Boolean} + * true if the permission is present (granted), false otherwise. + */ + var hasPermission = function hasPermission(permMap, type, identifier) { + + // If no identifier given, search ignoring the identifier + if (!identifier) + return containsPermission(permMap, type); + + // If identifier not present at all, there are no such permissions + if (!(identifier in permMap)) + return false; + + return permMap[identifier].indexOf(type) !== -1; + + }; + + /** + * Returns whether the given permission is granted for the connection + * having the given ID. + * + * @param {PermissionSet|Object} permSet + * The permission set to check. + * + * @param {String} type + * The permission to search for, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection to which the permission applies. + * + * @returns {Boolean} + * true if the permission is present (granted), false otherwise. + */ + PermissionSet.hasConnectionPermission = function hasConnectionPermission(permSet, type, identifier) { + return hasPermission(permSet.connectionPermissions, type, identifier); + }; + + /** + * Returns whether the given permission is granted for the connection group + * having the given ID. + * + * @param {PermissionSet|Object} permSet + * The permission set to check. + * + * @param {String} type + * The permission to search for, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the connection group to which the permission + * applies. + * + * @returns {Boolean} + * true if the permission is present (granted), false otherwise. + */ + PermissionSet.hasConnectionGroupPermission = function hasConnectionGroupPermission(permSet, type, identifier) { + return hasPermission(permSet.connectionGroupPermissions, type, identifier); + }; + + /** + * Returns whether the given permission is granted for the user having the + * given ID. + * + * @param {PermissionSet|Object} permSet + * The permission set to check. + * + * @param {String} type + * The permission to search for, as defined by + * PermissionSet.ObjectPermissionType. + * + * @param {String} identifier + * The identifier of the user to which the permission applies. + * + * @returns {Boolean} + * true if the permission is present (granted), false otherwise. + */ + PermissionSet.hasUserPermission = function hasUserPermission(permSet, type, identifier) { + return hasPermission(permSet.userPermissions, type, identifier); + }; + + /** + * Returns whether the given permission is granted at the system level. + * + * @param {PermissionSet|Object} permSet + * The permission set to check. + * + * @param {String} type + * The permission to search for, as defined by + * PermissionSet.SystemPermissionType. + * + * @returns {Boolean} + * true if the permission is present (granted), false otherwise. + */ + PermissionSet.hasSystemPermission = function hasSystemPermission(permSet, type) { + return permSet.systemPermissions.indexOf(type) !== -1; + }; + + return PermissionSet; + +}]); \ No newline at end of file From 4d255e21a1eb068f528ff5f0c13eaf351cf4b485 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 15 Dec 2014 16:11:43 -0800 Subject: [PATCH 10/37] GUAC-932: Get rid of APIConstants. --- .../net/basic/rest/APIConstants.java | 37 ------------------- .../basic/rest/connection/APIConnection.java | 4 +- .../connectiongroup/APIConnectionGroup.java | 8 +++- 3 files changed, 8 insertions(+), 41 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java deleted file mode 100644 index f6277eeaa..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/APIConstants.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest; - -/** - * Useful constants for the REST API. - * - * @author James Muehlner - */ -public class APIConstants { - - /** - * The identifier of the ROOT connection group. - */ - public static final String ROOT_CONNECTION_GROUP_IDENTIFIER = "ROOT"; - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java index dab0c3884..5ef022cd4 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java @@ -29,7 +29,7 @@ import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionRecord; -import org.glyptodon.guacamole.net.basic.rest.APIConstants; +import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; /** @@ -91,7 +91,7 @@ public class APIConnection { // Use the explicit ROOT group ID if (this.parentIdentifier == null) - this.parentIdentifier = APIConstants.ROOT_CONNECTION_GROUP_IDENTIFIER; + this.parentIdentifier = APIConnectionGroup.ROOT_IDENTIFIER; GuacamoleConfiguration configuration = connection.getConfiguration(); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java index 4ac155d39..16d2a0121 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java @@ -25,7 +25,6 @@ package org.glyptodon.guacamole.net.basic.rest.connectiongroup; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.ConnectionGroup.Type; -import org.glyptodon.guacamole.net.basic.rest.APIConstants; /** * A simple connection group to expose through the REST endpoints. @@ -35,6 +34,11 @@ import org.glyptodon.guacamole.net.basic.rest.APIConstants; @JsonIgnoreProperties(ignoreUnknown = true) public class APIConnectionGroup { + /** + * The identifier of the root connection group. + */ + public static final String ROOT_IDENTIFIER = "ROOT"; + /** * The name of this connection group. */ @@ -73,7 +77,7 @@ public class APIConnectionGroup { // Use the explicit ROOT group ID if (this.parentIdentifier == null) - this.parentIdentifier = APIConstants.ROOT_CONNECTION_GROUP_IDENTIFIER; + this.parentIdentifier = ROOT_IDENTIFIER; this.name = connectionGroup.getName(); this.type = connectionGroup.getType(); From ac6e92860b2aedbc156d31e1c3317b9f03690b9f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 16 Dec 2014 17:47:15 -0800 Subject: [PATCH 11/37] GUAC-932: Add support for recursive query (and break JS). --- .../guacamole/net/basic/rest/RESTModule.java | 2 - .../connectiongroup/APIConnectionGroup.java | 62 ++++++ .../ConnectionGroupRESTService.java | 189 ++++++++++++------ .../ConnectionGroupService.java | 59 ------ .../rest/services/connectionGroupService.js | 31 +-- .../webapp/app/rest/types/ConnectionGroup.js | 18 ++ 6 files changed, 227 insertions(+), 134 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index 48ac52b75..1c2a9d6c5 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -24,7 +24,6 @@ package org.glyptodon.guacamole.net.basic.rest; import com.google.inject.AbstractModule; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService; -import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupService; import org.glyptodon.guacamole.net.basic.rest.protocol.ProtocolRetrievalService; /** @@ -40,7 +39,6 @@ public class RESTModule extends AbstractModule { // Bind generic low-level services bind(ConnectionService.class); - bind(ConnectionGroupService.class); bind(ProtocolRetrievalService.class); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java index 16d2a0121..3f166f41c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/APIConnectionGroup.java @@ -22,9 +22,11 @@ package org.glyptodon.guacamole.net.basic.rest.connectiongroup; +import java.util.Collection; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.ConnectionGroup.Type; +import org.glyptodon.guacamole.net.basic.rest.connection.APIConnection; /** * A simple connection group to expose through the REST endpoints. @@ -58,6 +60,18 @@ public class APIConnectionGroup { * The type of this connection group. */ private Type type; + + /** + * All child connection groups. If children are not being queried, this may + * be omitted. + */ + private Collection childConnectionGroups; + + /** + * All child connections. If children are not being queried, this may be + * omitted. + */ + private Collection childConnections; /** * Create an empty APIConnectionGroup. @@ -148,4 +162,52 @@ public class APIConnectionGroup { this.type = type; } + /** + * Returns a collection of all child connection groups, or null if children + * have not been queried. + * + * @return + * A collection of all child connection groups, or null if children + * have not been queried. + */ + public Collection getChildConnectionGroups() { + return childConnectionGroups; + } + + /** + * Sets the collection of all child connection groups to the given + * collection, which may be null if children have not been queried. + * + * @param childConnectionGroups + * The collection containing all child connection groups of this + * connection group, or null if children have not been queried. + */ + public void setChildConnectionGroups(Collection childConnectionGroups) { + this.childConnectionGroups = childConnectionGroups; + } + + /** + * Returns a collection of all child connections, or null if children have + * not been queried. + * + * @return + * A collection of all child connections, or null if children have not + * been queried. + */ + public Collection getChildConnections() { + return childConnections; + } + + /** + * Sets the collection of all child connections to the given collection, + * which may be null if children have not been queried. + * + * @param childConnections + * The collection containing all child connections of this connection + * group, or null if children have not been queried. + */ + public void setChildConnections(Collection childConnections) { + this.childConnections = childConnections; + } + } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java index aa04a5f60..ee8b79a85 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java @@ -23,7 +23,8 @@ package org.glyptodon.guacamole.net.basic.rest.connectiongroup; import com.google.inject.Inject; -import java.util.List; +import java.util.ArrayList; +import java.util.Collection; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -37,12 +38,15 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; +import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.glyptodon.guacamole.net.basic.rest.connection.APIConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,55 +72,101 @@ public class ConnectionGroupRESTService { private AuthenticationService authenticationService; /** - * A service for managing the REST endpoint APIConnection objects. + * Retrieves the given connection group from the user context, including + * all descendant connections and groups if requested. + * + * @param userContext + * The user context from which to retrieve the connection group. + * + * @param identifier + * The unique identifier of the connection group to retrieve. + * + * @param includeDescendants + * Whether the descendant connections and groups of the given + * connection group should also be retrieved. + * + * @return + * The requested connection group, or null if no such connection group + * exists. + * + * @throws GuacamoleException + * If an error occurs while retrieving the requested connection group + * or any of its descendants. */ - @Inject - private ConnectionGroupService connectionGroupService; - - /** - * The ID that will be guaranteed to refer to the root connection group. - */ - private static final String ROOT_CONNECTION_GROUP_ID = "ROOT"; - - /** - * Gets a list of connection groups with the given ConnectionGroup parentID. - * If no parentID is provided, returns the connection groups from the root group. - * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param parentID The ID of the ConnectionGroup the connection groups - * belong to. If null, the root connection group will be used. - * @return The connection list. - * @throws GuacamoleException If a problem is encountered while listing connection groups. - */ - @GET - @AuthProviderRESTExposure - public List getConnectionGroups(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID) + private APIConnectionGroup retrieveConnectionGroup(UserContext userContext, + String identifier, boolean includeDescendants) throws GuacamoleException { - UserContext userContext = authenticationService.getUserContext(authToken); + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - // If the parent connection group is passed in, try to find it. - ConnectionGroup parentConnectionGroup; - if (parentID == null) - parentConnectionGroup = userContext.getRootConnectionGroup(); + ConnectionGroup connectionGroup; + + // Use root group if requested + if (identifier == null || identifier.equals(APIConnectionGroup.ROOT_IDENTIFIER)) + connectionGroup = rootGroup; + // Otherwise, query requested group using root group directory else { - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - parentConnectionGroup = connectionGroupDirectory.get(parentID); + + Directory connectionGroupDirectory = + rootGroup.getConnectionGroupDirectory(); + + // Get the connection group from root directory + connectionGroup = connectionGroupDirectory.get(identifier); + if (connectionGroup == null) + return null; + } - if (parentConnectionGroup == null) - throw new HTTPException(Status.NOT_FOUND, "No connection group found with the provided parentID."); + // Wrap queried connection group + APIConnectionGroup apiConnectionGroup = new APIConnectionGroup(connectionGroup); - Directory connectionGroupDirectory = - parentConnectionGroup.getConnectionGroupDirectory(); + // Recursively query all descendants if necessary + if (includeDescendants) { + + // Query all child connections + Collection apiConnections = new ArrayList(); + Directory connectionDirectory = connectionGroup.getConnectionDirectory(); + + for (String childIdentifier : connectionDirectory.getIdentifiers()) { + + // Pull current connection - silently ignore if connection was removed prior to read + Connection childConnection = connectionDirectory.get(childIdentifier); + if (childConnection == null) + continue; + + apiConnections.add(new APIConnection(childConnection)); + + } + + // Associate child connections with current connection group + apiConnectionGroup.setChildConnections(apiConnections); + + // Query all child connection groups + Collection apiConnectionGroups = new ArrayList(); + Directory groupDirectory = connectionGroup.getConnectionGroupDirectory(); + + for (String childIdentifier : groupDirectory.getIdentifiers()) { + + // Pull current connection group - silently ignore if connection group was removed prior to read + APIConnectionGroup childConnectionGroup = retrieveConnectionGroup(userContext, childIdentifier, true); + if (childConnectionGroup == null) + continue; + + apiConnectionGroups.add(childConnectionGroup); + + } + + // Associate child groups with current connection group + apiConnectionGroup.setChildConnectionGroups(apiConnectionGroups); + + } + + // Return the connectiion group + return apiConnectionGroup; - // Return the converted connection group list - return connectionGroupService.convertConnectionGroupList(connectionGroupDirectory); } - + /** * Gets an individual connection group. * @@ -133,27 +183,50 @@ public class ConnectionGroupRESTService { @PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException { UserContext userContext = authenticationService.getUserContext(authToken); - - // Get the connection group directory - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - - // Return the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) - return new APIConnectionGroup(rootGroup); - - Directory connectionGroupDirectory = - rootGroup.getConnectionGroupDirectory(); - // Get the connection group - ConnectionGroup connectionGroup = connectionGroupDirectory.get(connectionGroupID); + // Retrieve requested connection group only + APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, false); if (connectionGroup == null) - throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided ID."); + throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); - // Return the connectiion group - return new APIConnectionGroup(connectionGroup); + return connectionGroup; } - + + /** + * Gets an individual connection group and all children. + * + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param connectionGroupID + * The ID of the ConnectionGroup. + * + * @return + * The connection group. + * + * @throws GuacamoleException + * If a problem is encountered while retrieving the connection group or + * its descendants. + */ + @GET + @Path("/{connectionGroupID}/tree") + @AuthProviderRESTExposure + public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken, + @PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Retrieve requested connection group and all descendants + APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, true); + if (connectionGroup == null) + throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); + + return connectionGroup; + + } + /** * Deletes an individual connection group. * @@ -174,7 +247,7 @@ public class ConnectionGroupRESTService { ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) connectionGroupID = rootGroup.getIdentifier(); Directory connectionGroupDirectory = @@ -263,7 +336,7 @@ public class ConnectionGroupRESTService { ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) connectionGroupID = rootGroup.getIdentifier(); Directory connectionGroupDirectory = @@ -300,7 +373,7 @@ public class ConnectionGroupRESTService { ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); // Use the root group if it was asked for - if (connectionGroupID != null && connectionGroupID.equals(ROOT_CONNECTION_GROUP_ID)) + if (connectionGroupID != null && connectionGroupID.equals(APIConnectionGroup.ROOT_IDENTIFIER)) connectionGroupID = rootGroup.getIdentifier(); Directory connectionGroupDirectory = diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java deleted file mode 100644 index ecbe5fffd..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupService.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.connectiongroup; - -import java.util.ArrayList; -import java.util.List; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.ConnectionGroup; -import org.glyptodon.guacamole.net.auth.Directory; - -/** - * A service for performing useful manipulations on REST ConnectionGroups. - * - * @author James Muehlner - */ -public class ConnectionGroupService { - - /** - * Converts a ConnectionGroup directory to a list of APIConnectionGroup - * objects for exposing with the REST endpoints. - * - * @param connectionGroupDirectory The ConnectionGroup Directory to convert for REST endpoint use. - * @return A List of APIConnectionGroup objects for use with the REST endpoint. - * @throws GuacamoleException If an error occurs while converting the - * connection group directory. - */ - public List convertConnectionGroupList( - Directory connectionGroupDirectory) throws GuacamoleException { - - List restConnectionGroups = new ArrayList(); - - for (String connectionGroupID : connectionGroupDirectory.getIdentifiers()) - restConnectionGroups.add(new APIConnectionGroup(connectionGroupDirectory.get(connectionGroupID))); - - return restConnectionGroups; - - } - -} diff --git a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js index a2f23dfc3..d3f3ff68b 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js @@ -29,26 +29,26 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati var service = {}; /** - * Makes a request to the REST API to get the list of connection groups, - * returning a promise that provides an array of - * @link{ConnectionGroup} objects if successful. + * Makes a request to the REST API to get an individual connection group + * and all descendants, returning a promise that provides the corresponding + * @link{ConnectionGroup} if successful. Descendant groups and connections + * will be stored as children of that connection group. * - * @param {String} [parentID=ConnectionGroup.ROOT_IDENTIFIER] - * The ID of the connection group whose child connection groups should - * be returned. If not provided, the root connection group will be - * used by default. + * @param {String} [connectionGroupID=ConnectionGroup.ROOT_IDENTIFIER] + * The ID of the connection group to retrieve. If not provided, the + * root connection group will be retrieved by default. * - * @returns {Promise.} - * A promise which will resolve with an array of @link{ConnectionGroup} - * objects upon success. + * @returns {Promise.ConnectionGroup} + * A promise which will resolve with a @link{ConnectionGroup} upon + * success. */ - service.getConnectionGroups = function getConnectionGroups(parentID) { + service.getConnectionGroupTree = function getConnectionGroupTree(connectionGroupID) { - var parentIDParam = ""; - if (parentID) - parentIDParam = "&parentID=" + parentID; + // Use the root connection group ID if no ID is passed in + connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; - return $http.get("api/connectionGroup?token=" + authenticationService.getCurrentToken() + parentIDParam); + return $http.get("api/connectionGroup/" + connectionGroupID + "/tree?token=" + authenticationService.getCurrentToken()); + }; /** @@ -70,6 +70,7 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken()); + }; /** diff --git a/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js b/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js index f2c120c27..89e53c6eb 100644 --- a/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js +++ b/guacamole/src/main/webapp/app/rest/types/ConnectionGroup.js @@ -73,6 +73,24 @@ angular.module('rest').factory('ConnectionGroup', [function defineConnectionGrou */ this.type = template.type || ConnectionGroup.Type.ORGANIZATIONAL; + /** + * An array of all child connections, if known. This property may be + * null or undefined if children have not been queried, and thus the + * child connections are unknown. + * + * @type Connection[] + */ + this.childConnections = template.childConnections; + + /** + * An array of all child connection groups, if known. This property may + * be null or undefined if children have not been queried, and thus the + * child connection groups are unknown. + * + * @type ConnectionGroup[] + */ + this.childConnectionGroups = template.childConnectionGroups; + }; /** From 9b261f2a716e7a58d60efbb4262f0433148fb5b0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 16 Dec 2014 19:24:38 -0800 Subject: [PATCH 12/37] GUAC-932: Implement connection list directive. --- .../app/groupList/directives/guacGroupList.js | 72 +++++++ .../webapp/app/groupList/groupListModule.js | 27 +++ .../webapp/app/groupList/styles/groupList.css | 21 ++ .../groupList/templates/guacGroupList.html | 71 +++++++ .../app/groupList/types/GroupListItem.js | 181 ++++++++++++++++++ 5 files changed, 372 insertions(+) create mode 100644 guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js create mode 100644 guacamole/src/main/webapp/app/groupList/groupListModule.js create mode 100644 guacamole/src/main/webapp/app/groupList/styles/groupList.css create mode 100644 guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html create mode 100644 guacamole/src/main/webapp/app/groupList/types/GroupListItem.js diff --git a/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js new file mode 100644 index 000000000..42399fd2b --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/directives/guacGroupList.js @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which displays the contents of a connection group. + */ +angular.module('groupList').directive('guacGroupList', [function guacGroupList() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The connection group to display. + * + * @type ConnectionGroup|Object + */ + connectionGroup : '=' + + }, + + templateUrl: 'app/groupList/templates/guacGroupList.html', + controller: ['$scope', '$injector', '$interval', function guacGroupListController($scope, $injector, $interval) { + + // Get required types + var GroupListItem = $injector.get('GroupListItem'); + + // Set contents whenever the connection group is assigned or changed + $scope.$watch("connectionGroup", function setContents(connectionGroup) { + + if (connectionGroup) + $scope.rootItem = GroupListItem.fromConnectionGroup(connectionGroup); + else + $scope.rootItem = null; + + }); + + /** + * Toggle the open/closed status of a group list item. + * + * @param {GroupListItem} groupListItem + * The list item to expand, which should represent a + * connection group. + */ + $scope.toggleExpanded = function toggleExpanded(groupListItem) { + groupListItem.expanded = !groupListItem.expanded; + }; + + }] + + }; +}]); diff --git a/guacamole/src/main/webapp/app/groupList/groupListModule.js b/guacamole/src/main/webapp/app/groupList/groupListModule.js new file mode 100644 index 000000000..99841eeea --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/groupListModule.js @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Module for displaying the contents of a connection group, allowing the user + * to select individual connections or groups. + */ +angular.module('groupList', ['rest']); diff --git a/guacamole/src/main/webapp/app/groupList/styles/groupList.css b/guacamole/src/main/webapp/app/groupList/styles/groupList.css new file mode 100644 index 000000000..c6c47cc5c --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/styles/groupList.css @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ diff --git a/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html new file mode 100644 index 000000000..3c118b603 --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/templates/guacGroupList.html @@ -0,0 +1,71 @@ +
+ + + + +
+ +
diff --git a/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js b/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js new file mode 100644 index 000000000..d43387e88 --- /dev/null +++ b/guacamole/src/main/webapp/app/groupList/types/GroupListItem.js @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Provides the GroupListItem class definition. + */ +angular.module('groupList').factory('GroupListItem', ['ConnectionGroup', function defineGroupListItem(ConnectionGroup) { + + /** + * Creates a new GroupListItem, initializing the properties of that + * GroupListItem with the corresponding properties of the given template. + * + * @constructor + * @param {GroupListItem|Object} [template={}] + * The object whose properties should be copied within the new + * GroupListItem. + */ + var GroupListItem = function GroupListItem(template) { + + // Use empty object by default + template = template || {}; + + /** + * The unique identifier associated with the connection or connection + * group this item represents. + * + * @type String + */ + this.identifier = template.identifier; + + /** + * The human-readable display name of this item. + * + * @type String + */ + this.name = template.name; + + /** + * The unique identifier of the protocol, if this item represents a + * connection. If this item does not represent a connection, this + * property is not applicable. + * + * @type String + */ + this.protocol = template.protocol; + + /** + * All children items of this item. If this item contains no children, + * this will be an empty array. + * + * @type GroupListItem[] + */ + this.children = template.children || []; + + /** + * Whether this item represents a connection. If this item represents + * a connection group, this MUST be false. + * + * @type Boolean + */ + this.isConnection = template.isConnection; + + /** + * Whether this item represents a connection group. If this item + * represents a connection, this MUST be false. + * + * @type Boolean + */ + this.isConnectionGroup = template.isConnectionGroup; + + /** + * Whether this item represents a balancing connection group. + * + * @type Boolean + */ + this.isBalancing = template.isBalancing; + + /** + * Whether the children items should be displayed. + * + * @type Boolean + */ + this.isExpanded = template.isExpanded; + + }; + + /** + * Creates a new GroupListItem using the contents of the given connection. + * + * @param {ConnectionGroup} connection + * The connection whose contents should be represented by the new + * GroupListItem. + * + * @returns {GroupListItem} + * A new GroupListItem which represents the given connection. + */ + GroupListItem.fromConnection = function fromConnection(connection) { + + // Return item representing the given connection + return new GroupListItem({ + + // Identifying information + name : connection.name, + identifier : connection.identifier, + protocol : connection.protocol, + + // Type information + isConnection : true, + isConnectionGroup : false + + }); + + }; + + /** + * Creates a new GroupListItem using the contents and descendants of the + * given connection group. + * + * @param {ConnectionGroup} connectionGroup + * The connection group whose contents and descendants should be + * represented by the new GroupListItem and its descendants. + * + * @returns {GroupListItem} + * A new GroupListItem which represents the given connection group, + * including all descendants. + */ + GroupListItem.fromConnectionGroup = function fromConnectionGroup(connectionGroup) { + + var children = []; + + // Add any child connections + connectionGroup.childConnections.forEach(function addChildConnection(child) { + children.push(GroupListItem.fromConnection(child)); + }); + + // Add any child groups + connectionGroup.childConnectionGroups.forEach(function addChildGroup(child) { + children.push(GroupListItem.fromConnectionGroup(child)); + }); + + // Return item representing the given connection group + return new GroupListItem({ + + // Identifying information + name : connectionGroup.name, + identifier : connectionGroup.identifier, + + // Type information + isConnection : false, + isConnectionGroup : true, + isBalancing : connectionGroup.type === ConnectionGroup.Type.BALANCING, + + // Already-converted children + children : children + + }); + + }; + + return GroupListItem; + +}]); From 21ee9073d00997468a81df8442a82136c20db97b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 16 Dec 2014 19:25:20 -0800 Subject: [PATCH 13/37] GUAC-932: Migrate home to connection list directive. --- .../app/home/controllers/homeController.js | 70 ++++--------------- .../src/main/webapp/app/home/homeModule.js | 2 +- .../main/webapp/app/home/templates/home.html | 28 +------- 3 files changed, 16 insertions(+), 84 deletions(-) diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index ed3cb09b1..77c580b5e 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -25,71 +25,27 @@ */ angular.module('home').controller('homeController', ['$scope', '$injector', function homeController($scope, $injector) { + + // Get required types + var ConnectionGroup = $injector.get("ConnectionGroup"); - // The parameter name for getting the history from local storage - var GUAC_HISTORY_STORAGE_KEY = "GUAC_HISTORY"; - - // Get the dependencies commonJS style - var legacyConnectionGroupService = $injector.get("legacyConnectionGroupService"); - var guacHistory = $injector.get("guacHistory"); - - // All the connections and connection groups in root - $scope.connectionsAndGroups = []; + // Get required services + var connectionGroupService = $injector.get("connectionGroupService"), + guacHistory = $injector.get("guacHistory"); // All valid recent connections $scope.recentConnections = []; // Set status to loading until we have all the connections and groups loaded $scope.loading = true; - - /* Fetch all connections and groups, then find which recent connections - * still refer to valid connections and groups. - */ - legacyConnectionGroupService.getAllGroupsAndConnections($scope.connectionsAndGroups) - .then(function findRecentConnections() { - - // TODONT: Munch the guacHistory recentConnections list into a legacy-style object - var recentConnections = {}; - for (var i=0; i < guacHistory.recentConnections.length; i++) { - var entry = guacHistory.recentConnections[i]; - recentConnections[encodeURIComponent(entry.id)] = { - id : entry.id, - thumbnail : entry.thumbnail - }; - } - - // Figure out which recent connection entries are valid - $scope.connectionsAndGroups.forEach(function findValidEntries (connectionOrGroup) { - - var type = connectionOrGroup.isConnection ? "c" : "g"; - - // Find the unique ID to index into the recent connections - var uniqueId = encodeURIComponent( - type + "/" + connectionOrGroup.identifier - ); - - /* - * If it's a valid recent connection, add it to the list, - * along with enough information to make a connection url. - */ - var recentConnection = recentConnections[uniqueId]; - if(recentConnection) { - recentConnection.type = type; - recentConnection.id = connectionOrGroup.identifier; - $scope.recentConnections.push(recentConnection); - } - }); - + + // Retrieve root group and all descendants + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER) + .success(function rootGroupRetrieved(rootConnectionGroup) { + + $scope.rootConnectionGroup = rootConnectionGroup; $scope.loading = false; + }); - /** - * Toggle the open/closed status of the connectionGroup. - * - * @param {object} connectionGroup The connection group to toggle. - */ - $scope.toggleExpanded = function toggleExpanded(connectionGroup) { - connectionGroup.expanded = !connectionGroup.expanded; - }; - }]); diff --git a/guacamole/src/main/webapp/app/home/homeModule.js b/guacamole/src/main/webapp/app/home/homeModule.js index f0eb2f0d4..a3c840e21 100644 --- a/guacamole/src/main/webapp/app/home/homeModule.js +++ b/guacamole/src/main/webapp/app/home/homeModule.js @@ -20,4 +20,4 @@ * THE SOFTWARE. */ -angular.module('home', ['history', 'rest']); +angular.module('home', ['history', 'groupList', 'rest']); diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html index abbb095b0..f474ec68c 100644 --- a/guacamole/src/main/webapp/app/home/templates/home.html +++ b/guacamole/src/main/webapp/app/home/templates/home.html @@ -20,31 +20,6 @@ THE SOFTWARE. --> - -
@@ -73,6 +48,7 @@

{{'home.allConnections' | translate}}

-
+
+
\ No newline at end of file From ef5a9f960044a1d302feaa1e42f47746427f6b83 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 16 Dec 2014 21:30:54 -0800 Subject: [PATCH 14/37] GUAC-932: Add permission filtering to recursive connection group query. --- .../ConnectionGroupRESTService.java | 52 ++++++++++++++----- .../rest/services/connectionGroupService.js | 47 ++++++++++++++--- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java index ee8b79a85..b1cded3ca 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java @@ -42,7 +42,10 @@ import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.User; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.auth.permission.ConnectionPermission; +import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; @@ -84,6 +87,11 @@ public class ConnectionGroupRESTService { * @param includeDescendants * Whether the descendant connections and groups of the given * connection group should also be retrieved. + * + * @param permission + * The permission the current user must have for a connection or + * connection group to be returned in the results, if any. If null + * is specified, no filtering by permission will be performed. * * @return * The requested connection group, or null if no such connection group @@ -94,9 +102,10 @@ public class ConnectionGroupRESTService { * or any of its descendants. */ private APIConnectionGroup retrieveConnectionGroup(UserContext userContext, - String identifier, boolean includeDescendants) + String identifier, boolean includeDescendants, ObjectPermission.Type permission) throws GuacamoleException { + User self = userContext.self(); ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); ConnectionGroup connectionGroup; @@ -135,7 +144,9 @@ public class ConnectionGroupRESTService { if (childConnection == null) continue; - apiConnections.add(new APIConnection(childConnection)); + // Filter based on permission, if requested + if (permission == null || self.hasPermission(new ConnectionPermission(permission, childIdentifier))) + apiConnections.add(new APIConnection(childConnection)); } @@ -149,7 +160,7 @@ public class ConnectionGroupRESTService { for (String childIdentifier : groupDirectory.getIdentifiers()) { // Pull current connection group - silently ignore if connection group was removed prior to read - APIConnectionGroup childConnectionGroup = retrieveConnectionGroup(userContext, childIdentifier, true); + APIConnectionGroup childConnectionGroup = retrieveConnectionGroup(userContext, childIdentifier, true, permission); if (childConnectionGroup == null) continue; @@ -170,11 +181,18 @@ public class ConnectionGroupRESTService { /** * Gets an individual connection group. * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param connectionGroupID The ID of the ConnectionGroup. - * @return The connection group. - * @throws GuacamoleException If a problem is encountered while retrieving the connection group. + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param connectionGroupID + * The ID of the connection group to retrieve. + * + * @return + * The connection group, without any descendants. + * + * @throws GuacamoleException + * If a problem is encountered while retrieving the connection group. */ @GET @Path("/{connectionGroupID}") @@ -185,7 +203,7 @@ public class ConnectionGroupRESTService { UserContext userContext = authenticationService.getUserContext(authToken); // Retrieve requested connection group only - APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, false); + APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, false, null); if (connectionGroup == null) throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); @@ -201,10 +219,16 @@ public class ConnectionGroupRESTService { * performing the operation. * * @param connectionGroupID - * The ID of the ConnectionGroup. + * The ID of the connection group to retrieve. * + * @param permission + * If specified, limit the returned list to only those connections and + * connection groups for which the current user has the given + * permission. Otherwise, all visible connections and connection groups + * are returned. + * * @return - * The connection group. + * The requested connection group, including all descendants. * * @throws GuacamoleException * If a problem is encountered while retrieving the connection group or @@ -214,12 +238,14 @@ public class ConnectionGroupRESTService { @Path("/{connectionGroupID}/tree") @AuthProviderRESTExposure public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken, - @PathParam("connectionGroupID") String connectionGroupID) throws GuacamoleException { + @PathParam("connectionGroupID") String connectionGroupID, + @QueryParam("permission") ObjectPermission.Type permission) + throws GuacamoleException { UserContext userContext = authenticationService.getUserContext(authToken); // Retrieve requested connection group and all descendants - APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, true); + APIConnectionGroup connectionGroup = retrieveConnectionGroup(userContext, connectionGroupID, true, permission); if (connectionGroup == null) throw new GuacamoleResourceNotFoundException("No such connection group: \"" + connectionGroupID + "\""); diff --git a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js index d3f3ff68b..d99251f18 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionGroupService.js @@ -32,34 +32,55 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati * Makes a request to the REST API to get an individual connection group * and all descendants, returning a promise that provides the corresponding * @link{ConnectionGroup} if successful. Descendant groups and connections - * will be stored as children of that connection group. + * will be stored as children of that connection group. If a permission + * type is specified, the result will be filtering by that permission. * * @param {String} [connectionGroupID=ConnectionGroup.ROOT_IDENTIFIER] * The ID of the connection group to retrieve. If not provided, the * root connection group will be retrieved by default. - * + * + * @param {String} [permissionType] + * The permission type string of the permission that the current user + * must have for a given connection or connection group to appear + * within the result. Valid values are listed within + * PermissionSet.ObjectType. + * * @returns {Promise.ConnectionGroup} * A promise which will resolve with a @link{ConnectionGroup} upon * success. */ - service.getConnectionGroupTree = function getConnectionGroupTree(connectionGroupID) { + service.getConnectionGroupTree = function getConnectionGroupTree(connectionGroupID, permissionType) { // Use the root connection group ID if no ID is passed in connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; - - return $http.get("api/connectionGroup/" + connectionGroupID + "/tree?token=" + authenticationService.getCurrentToken()); + // Build HTTP parameters set + var httpParameters = { + token : authenticationService.getCurrentToken() + }; + + // Add permission filter if specified + if (permissionType) + httpParameters.permission = permissionType; + + // Retrieve connection group + return $http({ + method : 'GET', + url : 'api/connectionGroup/' + encodeURIComponent(connectionGroupID) + '/tree', + params : httpParameters + }); + }; /** * Makes a request to the REST API to get an individual connection group, * returning a promise that provides the corresponding * @link{ConnectionGroup} if successful. - * + * * @param {String} [connectionGroupID=ConnectionGroup.ROOT_IDENTIFIER] * The ID of the connection group to retrieve. If not provided, the * root connection group will be retrieved by default. - * + * * @returns {Promise.} A promise for the HTTP call. * A promise which will resolve with a @link{ConnectionGroup} upon * success. @@ -69,7 +90,17 @@ angular.module('rest').factory('connectionGroupService', ['$http', 'authenticati // Use the root connection group ID if no ID is passed in connectionGroupID = connectionGroupID || ConnectionGroup.ROOT_IDENTIFIER; - return $http.get("api/connectionGroup/" + connectionGroupID + "?token=" + authenticationService.getCurrentToken()); + // Build HTTP parameters set + var httpParameters = { + token : authenticationService.getCurrentToken() + }; + + // Retrieve connection group + return $http({ + method : 'GET', + url : 'api/connectionGroup/' + encodeURIComponent(connectionGroupID), + params : httpParameters + }); }; From c4057baa4213a608d53a8ce790647d16d160f6f9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 17 Dec 2014 00:21:48 -0800 Subject: [PATCH 15/37] GUAC-932: Replace use of legacy connection group filtering. --- .../manage/controllers/manageController.js | 35 +-- .../main/webapp/app/manage/manageModule.js | 2 +- .../webapp/app/manage/templates/manage.html | 24 +- .../services/legacyConnectionGroupService.js | 232 ------------------ 4 files changed, 12 insertions(+), 281 deletions(-) delete mode 100644 guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index 77ce0c77f..33bc9ce68 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -27,10 +27,11 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', function manageController($scope, $injector) { // Required types - var PermissionSet = $injector.get('PermissionSet'); + var PermissionSet = $injector.get('PermissionSet'); + var ConnectionGroup = $injector.get('ConnectionGroup'); // Required services - var legacyConnectionGroupService = $injector.get('legacyConnectionGroupService'); + var connectionGroupService = $injector.get('connectionGroupService'); var connectionEditModal = $injector.get('connectionEditModal'); var connectionGroupEditModal = $injector.get('connectionGroupEditModal'); var userEditModal = $injector.get('userEditModal'); @@ -41,34 +42,18 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', $scope.loadingUsers = true; $scope.loadingConnections = true; - // All the connections and connection groups in root - $scope.connectionsAndGroups = []; - - // All users that the current user has permission to edit - $scope.users = []; - $scope.basicPermissionsLoaded.then(function basicPermissionsHaveBeenLoaded() { - legacyConnectionGroupService.getAllGroupsAndConnections([], undefined, true, true).then(function filterConnectionsAndGroups(rootGroupList) { - $scope.rootGroup = rootGroupList[0]; - $scope.connectionsAndGroups = $scope.rootGroup.children; - - // Filter the items to only include ones that we have UPDATE for - if(!$scope.currentUserIsAdmin) { - legacyConnectionGroupService.filterConnectionsAndGroupByPermission( - $scope.connectionsAndGroups, - $scope.currentUserPermissions, - { - 'CONNECTION': 'UPDATE', - 'CONNECTION_GROUP': 'UPDATE' - } - ); - } - + + // Retrieve all users for whom we have UPDATE permission + connectionGroupService.getConnectionGroupTree(ConnectionGroup.ROOT_IDENTIFIER, PermissionSet.ObjectPermissionType.UPDATE) + .success(function connectionGroupReceived(rootGroup) { + $scope.rootGroup = rootGroup; $scope.loadingConnections = false; }); // Retrieve all users for whom we have UPDATE permission - userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE).success(function usersReceived(users) { + userService.getUsers(PermissionSet.ObjectPermissionType.UPDATE) + .success(function usersReceived(users) { $scope.users = users; $scope.loadingUsers = false; }); diff --git a/guacamole/src/main/webapp/app/manage/manageModule.js b/guacamole/src/main/webapp/app/manage/manageModule.js index 8dfae77c6..e6a83f6a9 100644 --- a/guacamole/src/main/webapp/app/manage/manageModule.js +++ b/guacamole/src/main/webapp/app/manage/manageModule.js @@ -23,5 +23,5 @@ /** * The module for the administration functionality. */ -angular.module('manage', ['btford.modal', 'rest', 'util']); +angular.module('manage', ['btford.modal', 'groupList', 'rest', 'util']); diff --git a/guacamole/src/main/webapp/app/manage/templates/manage.html b/guacamole/src/main/webapp/app/manage/templates/manage.html index f72142c25..f5edf2610 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manage.html +++ b/guacamole/src/main/webapp/app/manage/templates/manage.html @@ -20,28 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - -
{{'manage.back' | translate}} {{'home.logout' | translate}} @@ -85,7 +63,7 @@ THE SOFTWARE.
-
+
diff --git a/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js b/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js deleted file mode 100644 index e8eb9748b..000000000 --- a/guacamole/src/main/webapp/app/rest/services/legacyConnectionGroupService.js +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * A service for performing useful connection group related functionaltiy. - */ -angular.module('rest').factory('legacyConnectionGroupService', ['$injector', function legacyConnectionGroupService($injector) { - - // Get class dependencies - var PermissionSet = $injector.get("PermissionSet"); - - // Get services - var connectionGroupService = $injector.get('connectionGroupService'); - var connectionService = $injector.get('connectionService'); - var $q = $injector.get('$q'); - var displayObjectPreparationService = $injector.get('displayObjectPreparationService'); - - var service = {}; - - // Add all groups from this group to the parent group child list - function addToParent(connectionGroup, parentGroup, context, includeConnections) { - - // Include connections by default - if(typeof includeConnections === 'undefined') - includeConnections = true; - - parentGroup.children.push(connectionGroup); - - // Prepare this group for display - displayObjectPreparationService.prepareConnectionGroup(connectionGroup); - - if(includeConnections) { - // Get all connections in the group and add them under this connection group - context.openRequest(); - connectionService.getConnections(connectionGroup.identifier).success(function fetchConnections(connections) { - for(var i = 0; i < connections.length; i++) { - connections[i].isConnection = true; - connectionGroup.children.push(connections[i]); - } - context.closeRequest(); - }); - } - - // Get all connection groups in the group and repeat - context.openRequest(); - connectionGroupService.getConnectionGroups(connectionGroup.identifier).success(function fetchConnectionGroups(connectionGroups) { - for(var i = 0; i < connectionGroups.length; i++) { - addToParent(connectionGroups[i], connectionGroup, context, includeConnections); - } - context.closeRequest(); - }); - } - - /** - * Queries all connections and connection groups under the connection group - * with the provided parent ID, and returns them in a heirarchical structure - * with convinient display properties set on the objects. - * - * @param {array} items The root list of connections and groups. Should be an - * initally empty array that will get filled in as the - * connections and groups are loaded. - * - * @param {string} parentID The parent ID for the connection group. - * If not passed in, it will begin with - * the root connection group. - * - * @param {boolean} includeConnections Whether or not to include connections - * in the structure. Defaults to true. - * - * @param {boolean} includeRoot Whether or not to include the root connection group - * in the structure. Defaults to false. - * - * @return {promise} A promise that will be fulfilled when all connections - * and groups have been loaded. - */ - service.getAllGroupsAndConnections = function getAllGroupsAndConnections(items, parentID, includeConnections, includeRoot) { - - // Include connections by default - if(typeof includeConnections === 'undefined') - includeConnections = true; - - var context = { - // The number of requets to the server currently open - openRequests : 0, - - // Create the promise - finishedFetching : $q.defer(), - - // Notify the caller that the promise has been completed - complete : function complete() { - this.finishedFetching.resolve(items); - }, - - /** - * Indicate that a request has been started. - */ - openRequest : function openRequest() { - this.openRequests++; - }, - - /** - * Indicate that a request has been completed. If this was the last - * open request, fulfill the promise. - */ - closeRequest : function closeRequest() { - if(--this.openRequests === 0) - this.complete(); - } - }; - - // Include the root only if it was asked for - if(includeRoot) { - context.openRequest(); - connectionGroupService.getConnectionGroup(parentID).success(function setRootGroup (rootGroup) { - items.push(rootGroup); - rootGroup.children = []; - getChildrenOfRootGroup(rootGroup.children); - context.closeRequest(); - }); - } else { - getChildrenOfRootGroup(items); - } - - // Get the children of the root group - function getChildrenOfRootGroup(children) { - context.openRequest(); - connectionGroupService.getConnectionGroups(parentID).success(function fetchRootConnectionGroups(connectionGroups) { - for(var i = 0; i < connectionGroups.length; i++) { - addToParent(connectionGroups[i], {children: children}, context, includeConnections); - } - - if(includeConnections) { - // Get all connections in the root group and add them under this connection group - context.openRequest(); - connectionService.getConnections().success(function fetchRootConnections(connections) { - for(var i = 0; i < connections.length; i++) { - - // Prepare this connection for display - displayObjectPreparationService.prepareConnection(connections[i]); - - children.push(connections[i]); - } - context.closeRequest(); - }); - } - - context.closeRequest(); - }); - } - - // Return the promise - return context.finishedFetching.promise; - }; - - - /** - * Filters the list of connections and groups using the provided permissions. - * - * @param {array} items The heirarchical list of groups and connections. - * - * @param {object} permissionList The list of permissions to use - * when filtering. - * - * @param {object} permissionCriteria A map of object type to permission type(s) - * required for that object type. - * - * @return {array} The filtered list. - */ - service.filterConnectionsAndGroupByPermission = function filterConnectionsAndGroupByPermission(items, permissionList, permissionCriteria) { - var requiredConnectionPermission = permissionCriteria.CONNECTION; - var requiredConnectionGroupPermission = permissionCriteria.CONNECTION_GROUP; - - for(var i = 0; i < items.length; i++) { - var item = items[i]; - - if(item.isConnection && requiredConnectionPermission) { - - /* - * If item is a connection and a permission is required for this - * item, check now to see if the permission exists. If not, - * remove the item. - */ - if(!PermissionSet.hasConnectionPermission(permissionList, item.identifier, requiredConnectionPermission)) { - items.splice(i, 1); - continue; - } - } - else { - - /* - * If item is a group and a permission is required for this - * item, check now to see if the permission exists. If not, - * remove the item. - */ - if(requiredConnectionGroupPermission) { - if(!PermissionSet.hasConnectionGroupPermission(permissionList, item.identifier, requiredConnectionGroupPermission)) { - items.splice(i, 1); - continue; - } - } - - // Filter the children of this connection group as well - if(item.children && item.children.length) - service.filterConnectionsAndGroupByPermission(items.children); - } - } - - return items; - - }; - - return service; -}]); From 15f7fedd5335357662267f6462224da02fcfd2ce Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 17 Dec 2014 01:03:42 -0800 Subject: [PATCH 16/37] GUAC-932: Do not return parameters and history for all connections. Provide explicit endpoints for connection parameters and history. --- .../guacamole/net/basic/rest/RESTModule.java | 2 - .../basic/rest/connection/APIConnection.java | 35 ++--- .../rest/connection/APIConnectionWrapper.java | 27 ++-- .../connection/ConnectionRESTService.java | 137 ++++++++++++------ .../rest/connection/ConnectionService.java | 59 -------- .../app/rest/services/connectionService.js | 64 ++++---- .../main/webapp/app/rest/types/Connection.js | 17 +-- 7 files changed, 150 insertions(+), 191 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java index 1c2a9d6c5..70fde2592 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTModule.java @@ -23,7 +23,6 @@ package org.glyptodon.guacamole.net.basic.rest; import com.google.inject.AbstractModule; -import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionService; import org.glyptodon.guacamole.net.basic.rest.protocol.ProtocolRetrievalService; /** @@ -38,7 +37,6 @@ public class RESTModule extends AbstractModule { protected void configure() { // Bind generic low-level services - bind(ConnectionService.class); bind(ProtocolRetrievalService.class); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java index 5ef022cd4..57bf4a9c8 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnection.java @@ -22,13 +22,10 @@ package org.glyptodon.guacamole.net.basic.rest.connection; -import java.util.HashMap; -import java.util.List; import java.util.Map; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.Connection; -import org.glyptodon.guacamole.net.auth.ConnectionRecord; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; @@ -60,15 +57,10 @@ public class APIConnection { */ private String protocol; - /** - * The history records associated with this connection. - */ - private List history; - /** * Map of all associated parameter values, indexed by parameter name. */ - private Map parameters = new HashMap(); + private Map parameters; /** * Create an empty APIConnection. @@ -76,7 +68,9 @@ public class APIConnection { public APIConnection() {} /** - * Create an APIConnection from a Connection record. + * Create an APIConnection from a Connection record. Parameters for the + * connection will not be included. + * * @param connection The connection to create this APIConnection from. * @throws GuacamoleException If a problem is encountered while * instantiating this new APIConnection. @@ -84,21 +78,18 @@ public class APIConnection { public APIConnection(Connection connection) throws GuacamoleException { + // Set identifying information this.name = connection.getName(); this.identifier = connection.getIdentifier(); - this.parentIdentifier = connection.getParentIdentifier(); - this.history = connection.getHistory(); - // Use the explicit ROOT group ID + // Set proper parent identifier, using root identifier if needed + this.parentIdentifier = connection.getParentIdentifier(); if (this.parentIdentifier == null) this.parentIdentifier = APIConnectionGroup.ROOT_IDENTIFIER; - + + // Set protocol from configuration GuacamoleConfiguration configuration = connection.getConfiguration(); - this.protocol = configuration.getProtocol(); - - for (String key: configuration.getParameterNames()) - this.parameters.put(key, configuration.getParameter(key)); } @@ -151,14 +142,6 @@ public class APIConnection { this.parentIdentifier = parentIdentifier; } - /** - * Returns the history records associated with this connection. - * @return The history records associated with this connection. - */ - public List getHistory() { - return history; - } - /** * Returns the parameter map for this connection. * @return The parameter map for this connection. diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java index aca765c40..e38402ebc 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/APIConnectionWrapper.java @@ -22,6 +22,8 @@ package org.glyptodon.guacamole.net.basic.rest.connection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.glyptodon.guacamole.GuacamoleException; @@ -78,15 +80,16 @@ public class APIConnectionWrapper implements Connection { @Override public GuacamoleConfiguration getConfiguration() { - // Create the GuacamoleConfiguration from the parameter map + // Create the GuacamoleConfiguration with current protocol GuacamoleConfiguration configuration = new GuacamoleConfiguration(); - - Map parameters = apiConnection.getParameters(); - - for(Map.Entry entry : parameters.entrySet()) - configuration.setParameter(entry.getKey(), entry.getValue()); - configuration.setProtocol(apiConnection.getProtocol()); + + // Add parameters, if available + Map parameters = apiConnection.getParameters(); + if (parameters != null) { + for (Map.Entry entry : parameters.entrySet()) + configuration.setParameter(entry.getKey(), entry.getValue()); + } return configuration; } @@ -95,12 +98,14 @@ public class APIConnectionWrapper implements Connection { public void setConfiguration(GuacamoleConfiguration config) { // Create a parameter map from the GuacamoleConfiguration - Map parameters = apiConnection.getParameters(); - for(String key : config.getParameterNames()) + Map parameters = new HashMap(); + for (String key : config.getParameterNames()) parameters.put(key, config.getParameter(key)); - // Set the protocol + // Set protocol and parameters apiConnection.setProtocol(config.getProtocol()); + apiConnection.setParameters(parameters); + } @Override @@ -110,7 +115,7 @@ public class APIConnectionWrapper implements Connection { @Override public List getHistory() throws GuacamoleException { - return apiConnection.getHistory(); + return Collections.EMPTY_LIST; } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java index 103b56643..4e18741c2 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java @@ -23,7 +23,9 @@ package org.glyptodon.guacamole.net.basic.rest.connection; import com.google.inject.Inject; +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; @@ -39,11 +41,13 @@ import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; import org.glyptodon.guacamole.net.auth.Directory; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; import org.glyptodon.guacamole.net.basic.rest.HTTPException; import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; +import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,52 +72,6 @@ public class ConnectionRESTService { @Inject private AuthenticationService authenticationService; - /** - * A service for managing the REST endpoint APIConnection objects. - */ - @Inject - private ConnectionService connectionService; - - /** - * Gets a list of connections with the given ConnectionGroup parentID. - * If no parentID is provided, returns the connections from the root group. - * - * @param authToken The authentication token that is used to authenticate - * the user performing the operation. - * @param parentID The ID of the ConnectionGroup the connections - * belong to. If null, the root connection group will be used. - * @return The connection list. - * @throws GuacamoleException If a problem is encountered while listing connections. - */ - @GET - @AuthProviderRESTExposure - public List getConnections(@QueryParam("token") String authToken, @QueryParam("parentID") String parentID) - throws GuacamoleException { - - UserContext userContext = authenticationService.getUserContext(authToken); - - // If the parent connection group is passed in, try to find it. - ConnectionGroup parentConnectionGroup; - if (parentID == null) - parentConnectionGroup = userContext.getRootConnectionGroup(); - - else { - ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); - Directory connectionGroupDirectory = rootGroup.getConnectionGroupDirectory(); - parentConnectionGroup = connectionGroupDirectory.get(parentID); - } - - if (parentConnectionGroup == null) - throw new HTTPException(Status.NOT_FOUND, "No ConnectionGroup found with the provided parentID."); - - Directory connectionDirectory = - parentConnectionGroup.getConnectionDirectory(); - - // Return the converted connection directory - return connectionService.convertConnectionList(connectionDirectory); - - } - /** * Gets an individual connection. * @@ -144,7 +102,92 @@ public class ConnectionRESTService { return new APIConnection(connection); } - + + /** + * Retrieves the parameters associated with a single connection. + * + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param connectionID + * The ID of the connection. + * + * @return + * A map of parameter name/value pairs. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection parameters. + */ + @GET + @Path("/{connectionID}/parameters") + @AuthProviderRESTExposure + public Map getConnectionParameters(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionDirectory = + rootGroup.getConnectionDirectory(); + + // Get the connection + Connection connection = connectionDirectory.get(connectionID); + if (connection == null) + throw new HTTPException(Status.NOT_FOUND, "No Connection found with the provided ID."); + + // Retrieve connection configuration + GuacamoleConfiguration config = connection.getConfiguration(); + + // Convert parameters to map + Map parameters = new HashMap(); + for (String key : config.getParameterNames()) + parameters.put(key, config.getParameter(key)); + + return parameters; + + } + + /** + * Retrieves the usage history of a single connection. + * + * @param authToken + * The authentication token that is used to authenticate the user + * performing the operation. + * + * @param connectionID + * The ID of the connection. + * + * @return + * A list of connection records, describing the start and end times of + * various usages of this connection. + * + * @throws GuacamoleException + * If an error occurs while retrieving the connection history. + */ + @GET + @Path("/{connectionID}/history") + @AuthProviderRESTExposure + public List getConnectionHistory(@QueryParam("token") String authToken, + @PathParam("connectionID") String connectionID) throws GuacamoleException { + + UserContext userContext = authenticationService.getUserContext(authToken); + + // Get the connection directory + ConnectionGroup rootGroup = userContext.getRootConnectionGroup(); + Directory connectionDirectory = + rootGroup.getConnectionDirectory(); + + // Get the connection + Connection connection = connectionDirectory.get(connectionID); + if (connection == null) + throw new HTTPException(Status.NOT_FOUND, "No Connection found with the provided ID."); + + return connection.getHistory(); + + } + /** * Deletes an individual connection. * diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java deleted file mode 100644 index 44449818d..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionService.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 Glyptodon LLC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.connection; - -import java.util.ArrayList; -import java.util.List; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.auth.Connection; -import org.glyptodon.guacamole.net.auth.Directory; - -/** - * A service for performing useful manipulations on REST Connections. - * - * @author James Muehlner - */ -public class ConnectionService { - - /** - * Converts a Connection Directory to a list of APIConnection objects for - * exposing with the REST endpoints. - * - * @param connectionDirectory The Connection Directory to convert for REST endpoint use. - * @return A List of APIConnection objects for use with the REST endpoint. - * @throws GuacamoleException If an error occurs while converting the - * connection directory. - */ - public List convertConnectionList(Directory connectionDirectory) - throws GuacamoleException { - - List restConnections = new ArrayList(); - - for (String connectionID : connectionDirectory.getIdentifiers()) - restConnections.add(new APIConnection(connectionDirectory.get(connectionID))); - - return restConnections; - - } - -} diff --git a/guacamole/src/main/webapp/app/rest/services/connectionService.js b/guacamole/src/main/webapp/app/rest/services/connectionService.js index 3c11fb4e4..190f9ef5c 100644 --- a/guacamole/src/main/webapp/app/rest/services/connectionService.js +++ b/guacamole/src/main/webapp/app/rest/services/connectionService.js @@ -48,28 +48,37 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer }; /** - * Makes a request to the REST API to get the list of connections, - * returning a promise that provides an array of - * @link{Connection} objects if successful. + * Makes a request to the REST API to get the usage history of a single + * connection, returning a promise that provides the corresponding + * array of @link{ConnectionHistoryEntry} objects if successful. * - * @param {String} [parentID=ConnectionGroup.ROOT_IDENTIFIER] - * The ID of the connection group whose child connections should be - * returned. If not provided, the root connection group will be used - * by default. - * - * @returns {Promise.} - * A promise which will resolve with an array of @link{Connection} - * objects upon success. + * @param {String} id + * The identifier of the connection. + * + * @returns {Promise.} + * A promise which will resolve with an array of + * @link{ConnectionHistoryEntry} objects upon success. */ - service.getConnections = function getConnections(parentID) { - - var parentIDParam = ""; - if (parentID) - parentIDParam = "&parentID=" + parentID; - - return $http.get("api/connection?token=" + authenticationService.getCurrentToken() + parentIDParam); + service.getConnectionHistory = function getConnectionHistory(id) { + return $http.get("api/connection/" + id + "/history?token=" + authenticationService.getCurrentToken()); }; - + + /** + * Makes a request to the REST API to get the parameters of a single + * connection, returning a promise that provides the corresponding + * map of parameter name/value pairs if successful. + * + * @param {String} id + * The identifier of the connection. + * + * @returns {Promise.>} + * A promise which will resolve with an map of parameter name/value + * pairs upon success. + */ + service.getConnectionParameters = function getConnectionParameters(id) { + return $http.get("api/connection/" + id + "/parameters?token=" + authenticationService.getCurrentToken()); + }; + /** * Makes a request to the REST API to save a connection, returning a * promise that can be used for processing the results of the call. If the @@ -85,18 +94,9 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer */ service.saveConnection = function saveConnection(connection) { - /* - * FIXME: This should not be necessary. Perhaps the need for this is a - * sign that history should be queried separately, and not reside as - * part of the connection object? - */ - // Do not try to save the connection history records - var connectionToSave = angular.copy(connection); - delete connectionToSave.history; - // If connection is new, add it and set the identifier automatically - if (!connectionToSave.identifier) { - return $http.post("api/connection/?token=" + authenticationService.getCurrentToken(), connectionToSave).success( + if (!connection.identifier) { + return $http.post("api/connection/?token=" + authenticationService.getCurrentToken(), connection).success( // Set the identifier on the new connection function setConnectionID(connectionID){ @@ -109,9 +109,9 @@ angular.module('rest').factory('connectionService', ['$http', 'authenticationSer // Otherwise, update the existing connection else { return $http.post( - "api/connection/" + connectionToSave.identifier + + "api/connection/" + connection.identifier + "?token=" + authenticationService.getCurrentToken(), - connectionToSave); + connection); } }; diff --git a/guacamole/src/main/webapp/app/rest/types/Connection.js b/guacamole/src/main/webapp/app/rest/types/Connection.js index a41cba70f..656cf3fab 100644 --- a/guacamole/src/main/webapp/app/rest/types/Connection.js +++ b/guacamole/src/main/webapp/app/rest/types/Connection.js @@ -70,26 +70,15 @@ angular.module('rest').factory('Connection', [function defineConnection() { */ this.protocol = template.protocol; - /** - * All previous and current usages of this connection, along with - * associated users. - * - * @type ConnectionHistoryEntry[] - * @default [] - */ - this.history = template.history || []; - /** * Connection configuration parameters, as dictated by the protocol in * use, arranged as name/value pairs. This information may not be - * available if the current user lacks permission to update - * connection parameters, even if they otherwise have permission to - * read and use the connection. + * available until directly queried. If this information is + * unavailable, this property will be null or undefined. * * @type Object. - * @default {} */ - this.parameters = template.parameters || {}; + this.parameters = template.parameters; }; From b0ad6f45ecc08b59804b6572dd22df5392a23436 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 17 Dec 2014 10:52:03 -0800 Subject: [PATCH 17/37] GUAC-932: Ignore the Netbeans customs.json HTML element/attribute validation file. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d26be9c0e..b82b2caa7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *~ target/ nb-configuration.xml +guacamole/customs.json From db8b3230cfc38e3f8073784c16b2585a5f3ae97c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 17 Dec 2014 11:12:32 -0800 Subject: [PATCH 18/37] GUAC-932: Fix bad closing tag in index.html. --- guacamole/src/main/webapp/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 472e18ca6..0f6971afb 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -48,7 +48,7 @@ THE SOFTWARE.
-
+
From 0dd2cd87fbf03cdd85029ae90f6fd9000326de54 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 17 Dec 2014 11:15:11 -0800 Subject: [PATCH 19/37] GUAC-932: Fix validation in client.html. --- .../src/main/webapp/app/client/templates/client.html | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 74eeb4b7b..4402a0bbc 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -33,10 +33,7 @@
-
- - -
+