From 094059c50df6da12a6cf267305c27264078dc704 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 12:06:20 -0700 Subject: [PATCH 01/14] GUAC-586: Refactor user permission checks in management UI to use data source identifiers. --- .../controllers/manageUserController.js | 222 ++++++++++++------ 1 file changed, 150 insertions(+), 72 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 6b927bdf6..a8032c2f8 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -79,7 +79,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto * * @type String */ - var dataSource = $routeParams.dataSource; + var selectedDataSource = $routeParams.dataSource; /** * The username of the user being edited. @@ -89,14 +89,13 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto var username = $routeParams.id; /** - * Whether the user being modified actually exists. If the user does not - * yet exist, a different REST service call must be made to create that - * user rather than update an existing user. If the user has not yet been - * loaded, this will be null. + * All user accounts associated with the same username as the account being + * created or edited, as a map of data source identifier to the User object + * within that data source. * - * @type Boolean + * @type Object. */ - var userExists = null; + $scope.users = null; /** * The user being modified. @@ -122,10 +121,11 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto $scope.rootGroups = null; /** - * All permissions associated with the current user, or null if the user's - * permissions have not yet been loaded. + * A map of data source identifiers to the set of all permissions + * associated with the current user under that data source, or null if the + * user's permissions have not yet been loaded. * - * @type PermissionSet + * @type Object. */ $scope.permissions = null; @@ -155,7 +155,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto */ $scope.isLoaded = function isLoaded() { - return $scope.user !== null + return $scope.users !== null && $scope.permissionFlags !== null && $scope.rootGroups !== null && $scope.permissions !== null @@ -163,184 +163,237 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }; + /** + * Returns whether the user being edited already exists within the data + * source specified. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. + * + * @returns {Boolean} + * true if the user being edited already exists, false otherwise. + */ + $scope.userExists = function userExists(dataSource) { + + // Do not check if users are not yet loaded + if (!$scope.users) + return false; + + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + + // Account exists only if it was successfully retrieved + return (dataSource in $scope.users); + + }; + /** * Returns whether the current user can change attributes associated with - * the user being edited. + * the user being edited within the given data source. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. * * @returns {Boolean} * true if the current user can change attributes associated with the * user being edited, false otherwise. */ - $scope.canChangeAttributes = function canChangeAttributes() { + $scope.canChangeAttributes = function canChangeAttributes(dataSource) { // Do not check if permissions are not yet loaded if (!$scope.permissions) return false; + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + // Attributes can always be set if we are creating the user - if (!userExists) + if (!$scope.userExists(dataSource)) return true; // The administrator can always change attributes - if (PermissionSet.hasSystemPermission($scope.permissions, + if (PermissionSet.hasSystemPermission($scope.permissions[dataSource], PermissionSet.SystemPermissionType.ADMINISTER)) return true; // Otherwise, can change attributes if we have permission to update this user - return PermissionSet.hasUserPermission($scope.permissions, + return PermissionSet.hasUserPermission($scope.permissions[dataSource], PermissionSet.ObjectPermissionType.UPDATE, username); }; /** * Returns whether the current user can change permissions of any kind - * which are associated with the user being edited. + * which are associated with the user being edited within the given data + * source. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. * * @returns {Boolean} * true if the current user can grant or revoke permissions of any kind * which are associated with the user being edited, false otherwise. */ - $scope.canChangePermissions = function canChangePermissions() { + $scope.canChangePermissions = function canChangePermissions(dataSource) { // Do not check if permissions are not yet loaded if (!$scope.permissions) return false; + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + // Permissions can always be set if we are creating the user - if (!userExists) + if (!$scope.userExists(dataSource)) return true; // The administrator can always modify permissions - if (PermissionSet.hasSystemPermission($scope.permissions, + if (PermissionSet.hasSystemPermission($scope.permissions[dataSource], PermissionSet.SystemPermissionType.ADMINISTER)) return true; // Otherwise, can only modify permissions if we have explicit // ADMINISTER permission - return PermissionSet.hasUserPermission($scope.permissions, + return PermissionSet.hasUserPermission($scope.permissions[dataSource], PermissionSet.ObjectPermissionType.ADMINISTER, username); }; /** * Returns whether the current user can change the system permissions - * granted to the user being edited. + * granted to the user being edited within the given data source. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. * * @returns {Boolean} * true if the current user can grant or revoke system permissions to * the user being edited, false otherwise. */ - $scope.canChangeSystemPermissions = function canChangeSystemPermissions() { + $scope.canChangeSystemPermissions = function canChangeSystemPermissions(dataSource) { // Do not check if permissions are not yet loaded if (!$scope.permissions) return false; + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + // Only the administrator can modify system permissions - return PermissionSet.hasSystemPermission($scope.permissions, + return PermissionSet.hasSystemPermission($scope.permissions[dataSource], PermissionSet.SystemPermissionType.ADMINISTER); }; /** - * Returns whether the current user can save the user being edited. Saving - * will create or update that user depending on whether the user already - * exists. + * Returns whether the current user can save the user being edited within + * the given data source. Saving will create or update that user depending + * on whether the user already exists. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. * * @returns {Boolean} * true if the current user can save changes to the user being edited, * false otherwise. */ - $scope.canSaveUser = function canSaveUser() { + $scope.canSaveUser = function canSaveUser(dataSource) { // Do not check if permissions are not yet loaded if (!$scope.permissions) return false; + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + // The administrator can always save users - if (PermissionSet.hasSystemPermission($scope.permissions, + if (PermissionSet.hasSystemPermission($scope.permissions[dataSource], PermissionSet.SystemPermissionType.ADMINISTER)) return true; // If user does not exist, can only save if we have permission to create users - if (!userExists) - return PermissionSet.hasSystemPermission($scope.permissions, + if (!$scope.userExists(dataSource)) + return PermissionSet.hasSystemPermission($scope.permissions[dataSource], PermissionSet.SystemPermissionType.CREATE_USER); // Otherwise, can only save if we have permission to update this user - return PermissionSet.hasUserPermission($scope.permissions, + return PermissionSet.hasUserPermission($scope.permissions[dataSource], PermissionSet.ObjectPermissionType.UPDATE, username); }; /** - * Returns whether the current user can delete the user being edited. + * Returns whether the current user can delete the user being edited from + * the given data source. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. * * @returns {Boolean} * true if the current user can delete the user being edited, false * otherwise. */ - $scope.canDeleteUser = function canDeleteUser() { + $scope.canDeleteUser = function canDeleteUser(dataSource) { // Do not check if permissions are not yet loaded if (!$scope.permissions) return false; + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + // Can't delete what doesn't exist - if (!userExists) + if (!$scope.userExists(dataSource)) return false; // The administrator can always delete users - if (PermissionSet.hasSystemPermission($scope.permissions, + if (PermissionSet.hasSystemPermission($scope.permissions[dataSource], PermissionSet.SystemPermissionType.ADMINISTER)) return true; // Otherwise, require explicit DELETE permission on the user - return PermissionSet.hasUserPermission($scope.permissions, + return PermissionSet.hasUserPermission($scope.permissions[dataSource], PermissionSet.ObjectPermissionType.DELETE, username); }; /** - * Returns whether the user being edited is read-only, and thus cannot be - * modified by the current user. + * Returns whether the user being edited within the given data source is + * read-only, and thus cannot be modified by the current user. + * + * @param {String} [dataSource] + * The identifier of the data source to check. If omitted, this will + * default to the currently-selected data source. * * @returns {Boolean} * true if the user being edited is actually read-only and cannot be * edited at all, false otherwise. */ - $scope.isReadOnly = function isReadOnly() { - return !$scope.canSaveUser(); + $scope.isReadOnly = function isReadOnly(dataSource) { + + // Use currently-selected data source if unspecified + dataSource = dataSource || selectedDataSource; + + // User is read-only if they cannot be saved + return !$scope.canSaveUser(dataSource); + }; - // Pull user attribute schema - schemaService.getUserAttributes(dataSource).success(function attributesReceived(attributes) { - $scope.attributes = attributes; - }); - - // Pull user data - dataSourceService.apply(userService.getUser, dataSources, username) - .then(function usersReceived(users) { - - // Get user for currently-selected data source - $scope.user = users[dataSource]; - - // Create skeleton user if user does not exist - if (!$scope.user) { - userExists = false; - $scope.user = new User({ - 'username' : username - }); - } - else - userExists = true; + // Update visible account pages whenever available users/permissions changes + $scope.$watchGroup(['users', 'permissions'], function updateAccountPages() { // Generate pages for each applicable data source $scope.accountPages = []; angular.forEach(dataSources, function addAccountPage(dataSource) { // Determine whether data source contains this user - var linked = dataSource in users; + var linked = $scope.userExists(dataSource); // Add page entry $scope.accountPages.push(new PageDefinition({ @@ -353,8 +406,29 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); + // Pull user attribute schema + schemaService.getUserAttributes(selectedDataSource).success(function attributesReceived(attributes) { + $scope.attributes = attributes; + }); + + // Pull user data + dataSourceService.apply(userService.getUser, dataSources, username) + .then(function usersReceived(users) { + + // Get user for currently-selected data source + $scope.users = users; + $scope.user = users[selectedDataSource]; + + // Create skeleton user if user does not exist + if (!$scope.user) + $scope.user = new User({ + 'username' : username + }); + + }); + // Pull user permissions - permissionService.getPermissions(dataSource, username).success(function gotPermissions(permissions) { + permissionService.getPermissions(selectedDataSource, username).success(function gotPermissions(permissions) { $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions); }) @@ -366,7 +440,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Retrieve all connections for which we have ADMINISTER permission dataSourceService.apply( connectionGroupService.getConnectionGroupTree, - [dataSource], + [selectedDataSource], ConnectionGroup.ROOT_IDENTIFIER, [PermissionSet.ObjectPermissionType.ADMINISTER] ) @@ -375,8 +449,12 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); // Query the user's permissions for the current user - permissionService.getPermissions(dataSource, currentUsername) - .success(function permissionsReceived(permissions) { + dataSourceService.apply( + permissionService.getPermissions, + dataSources, + currentUsername + ) + .then(function permissionsReceived(permissions) { $scope.permissions = permissions; }); @@ -716,15 +794,15 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Save or create the user, depending on whether the user exists var saveUserPromise; - if (userExists) - saveUserPromise = userService.saveUser(dataSource, $scope.user); + if ($scope.userExists(selectedDataSource)) + saveUserPromise = userService.saveUser(selectedDataSource, $scope.user); else - saveUserPromise = userService.createUser(dataSource, $scope.user); + saveUserPromise = userService.createUser(selectedDataSource, $scope.user); saveUserPromise.success(function savedUser() { // Upon success, save any changed permissions - permissionService.patchPermissions(dataSource, $scope.user.username, permissionsAdded, permissionsRemoved) + permissionService.patchPermissions(selectedDataSource, $scope.user.username, permissionsAdded, permissionsRemoved) .success(function patchedUserPermissions() { $location.path('/settings/users'); }) @@ -786,7 +864,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto var deleteUserImmediately = function deleteUserImmediately() { // Delete the user - userService.deleteUser(dataSource, $scope.user) + userService.deleteUser(selectedDataSource, $scope.user) .success(function deletedUser() { $location.path('/settings/users'); }) From 12a1cded21fae7d4ff3c34dc179224861c4fc07c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 12:37:45 -0700 Subject: [PATCH 02/14] GUAC-586: Do not include tabs for accounts that do not exist and cannot be created. Style read-only accounts differently. --- .../manage/controllers/manageUserController.js | 16 ++++++++++++++-- .../webapp/app/manage/styles/manage-user.css | 14 ++++++++++---- guacamole/src/main/webapp/images/lock.png | Bin 0 -> 511 bytes 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 guacamole/src/main/webapp/images/lock.png diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index a8032c2f8..969e49351 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -393,13 +393,25 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto angular.forEach(dataSources, function addAccountPage(dataSource) { // Determine whether data source contains this user - var linked = $scope.userExists(dataSource); + var linked = $scope.userExists(dataSource); + var readOnly = $scope.isReadOnly(dataSource); + + // Account is not relevant if it does not exist and cannot be + // created + if (!linked && readOnly) + return; + + // Determine class name based on read-only / linked status + var className; + if (readOnly) className = 'read-only'; + else if (linked) className = 'linked'; + else className = 'unlinked'; // Add page entry $scope.accountPages.push(new PageDefinition({ name : translationStringService.canonicalize('DATA_SOURCE_' + dataSource) + '.NAME', url : '/manage/' + encodeURIComponent(dataSource) + '/users/' + encodeURIComponent(username), - className : linked ? 'linked' : 'unlinked' + className : className })); }); diff --git a/guacamole/src/main/webapp/app/manage/styles/manage-user.css b/guacamole/src/main/webapp/app/manage/styles/manage-user.css index 7d6f077db..ab64e78b7 100644 --- a/guacamole/src/main/webapp/app/manage/styles/manage-user.css +++ b/guacamole/src/main/webapp/app/manage/styles/manage-user.css @@ -28,14 +28,16 @@ text-transform: none; } -.manage-user .page-tabs .page-list li.unlinked a[href], -.manage-user .page-tabs .page-list li.linked a[href] { +.manage-user .page-tabs .page-list li.read-only a[href], +.manage-user .page-tabs .page-list li.unlinked a[href], +.manage-user .page-tabs .page-list li.linked a[href] { padding-right: 2.5em; position: relative; } -.manage-user .page-tabs .page-list li.unlinked a[href]:before, -.manage-user .page-tabs .page-list li.linked a[href]:before { +.manage-user .page-tabs .page-list li.read-only a[href]:before, +.manage-user .page-tabs .page-list li.unlinked a[href]:before, +.manage-user .page-tabs .page-list li.linked a[href]:before { content: ' '; position: absolute; right: 0; @@ -47,6 +49,10 @@ background-position: center; } +.manage-user .page-tabs .page-list li.read-only a[href]:before { + background-image: url('images/lock.png'); +} + .manage-user .page-tabs .page-list li.unlinked a[href]:before { background-image: url('images/plus.png'); } diff --git a/guacamole/src/main/webapp/images/lock.png b/guacamole/src/main/webapp/images/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..399d511829bbf323504b3ef94242eac1c5d1c073 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4-S9CC6C{4-7P-4OWT0)!hZ9 z6)As$bD7WfZ1jDgmHfw9*ONELXx~h}{qd!|mzF!)tWTYF<%H&A>6=;x`PRwzIs@G< z?|u_?rJi{n_nB&@D$DH9W1AQ}ul(0A+%Eq$Jnf9p#8k#d?|*GLa;IX=&C6LUAAL1z zkiB&1nE5&u3!NQ)HD9+Jx|9EauVSX%qPebJdtaTqRrN5Y-mmHJGk4v6PRq>1-{0CC zz_^FGBA6ILD<(VOVN~V5ESqr6>q}ZU{L(FO`1OBb2L$-MXWt&Ss-EY%Qz$U97(8A5 KT-G@yGywnxl+HT< literal 0 HcmV?d00001 From 2dcadd584ebc8bf0b7927366edd7eacd8bd6dbfa Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 12:41:41 -0700 Subject: [PATCH 03/14] GUAC-586: Do not attempt to create users with blank/whitespace usernames. --- .../main/webapp/app/settings/directives/guacSettingsUsers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index af52ee3bf..4463a138d 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -249,7 +249,9 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings * username specified. */ $scope.newUser = function newUser() { - $location.url('/manage/' + encodeURIComponent(getDefaultDataSource()) + '/users/' + encodeURIComponent($scope.newUsername)); + var username = $scope.newUsername.trim(); + if (username) + $location.url('/manage/' + encodeURIComponent(getDefaultDataSource()) + '/users/' + encodeURIComponent(username)); }; }] From 873de9ddbffa876c38483ecfbda7f3f505809156 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 13:40:14 -0700 Subject: [PATCH 04/14] GUAC-586: Link to the first editable user account by default. --- .../app/settings/directives/guacSettingsUsers.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index 4463a138d..5802145fa 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -194,7 +194,11 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings }; // Retrieve current permissions - dataSourceService.apply(permissionService.getPermissions, dataSources, currentUsername) + dataSourceService.apply( + permissionService.getPermissions, + dataSources, + currentUsername + ) .then(function permissionsRetrieved(permissions) { // Store retrieved permissions @@ -230,6 +234,12 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings if (addedUsers[user.username]) return; + // Link to default creation data source if we cannot manage this user + if (!PermissionSet.hasSystemPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.ADMINISTER) + && !PermissionSet.hasUserPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.UPDATE, user.username) + && !PermissionSet.hasUserPermission(permissions[dataSource], PermissionSet.ObjectPermissionType.DELETE, user.username)) + dataSource = getDefaultDataSource(); + // Add user to overall list addedUsers[user.username] = user; $scope.manageableUsers.push(new ManageableUser ({ From 7ea05b14f300c03464f043ed999d427a2010fa50 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 13:49:16 -0700 Subject: [PATCH 05/14] GUAC-586: Do not grant UPDATE on self by default. --- .../org/glyptodon/guacamole/auth/jdbc/user/UserService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java index 74b00c21d..6f609b2b8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java @@ -69,8 +69,7 @@ public class UserService extends ModeledDirectoryObjectService Date: Thu, 3 Sep 2015 13:53:40 -0700 Subject: [PATCH 06/14] GUAC-586: Fix logic error in home page calculation. --- .../main/webapp/app/navigation/services/userPageService.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 27878ceb8..782f3fce7 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -78,9 +78,12 @@ angular.module('navigation').factory('userPageService', ['$injector', var connections = rootGroup.childConnections || []; var connectionGroups = rootGroup.childConnectionGroups || []; + // Calculate total number of root-level objects + var totalRootObjects = connections.length + connectionGroups.length; + // If exactly one connection or balancing group is available, use // that as the home page - if (homePage === null && connections.length + connectionGroups.length === 1) { + if (homePage === null && totalRootObjects === 1) { var connection = connections[0]; var connectionGroup = connectionGroups[0]; @@ -116,7 +119,7 @@ angular.module('navigation').factory('userPageService', ['$injector', // Otherwise, a connection or balancing group cannot serve as the // home page - else { + else if (totalRootObjects >= 1) { homePage = null; break; } From dbc94261ee5501eefae29d88e88fa414376f92fe Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 14:06:00 -0700 Subject: [PATCH 07/14] GUAC-586: Ignore possible lack of specific permission sets. --- .../main/webapp/app/rest/types/PermissionSet.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js index 7f61f3d02..c7e92378f 100644 --- a/guacamole/src/main/webapp/app/rest/types/PermissionSet.js +++ b/guacamole/src/main/webapp/app/rest/types/PermissionSet.js @@ -191,6 +191,10 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() */ var hasPermission = function hasPermission(permMap, type, identifier) { + // No permission if no permission map at all + if (!permMap) + return false; + // If no identifier given, search ignoring the identifier if (!identifier) return containsPermission(permMap, type); @@ -303,6 +307,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * true if the permission is present (granted), false otherwise. */ PermissionSet.hasSystemPermission = function hasSystemPermission(permSet, type) { + if (!permSet.systemPermissions) return false; return permSet.systemPermissions.indexOf(type) !== -1; }; @@ -324,6 +329,8 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() */ PermissionSet.addSystemPermission = function addSystemPermission(permSet, type) { + permSet.systemPermissions = permSet.systemPermissions || []; + // Add permission, if it doesn't already exist if (permSet.systemPermissions.indexOf(type) === -1) { permSet.systemPermissions.push(type); @@ -352,6 +359,8 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() */ PermissionSet.removeSystemPermission = function removeSystemPermission(permSet, type) { + permSet.systemPermissions = permSet.systemPermissions || []; + // Remove permission, if it exists var permLocation = permSet.systemPermissions.indexOf(type); if (permLocation !== -1) { @@ -463,6 +472,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * already present in the given permission set. */ PermissionSet.addConnectionPermission = function addConnectionPermission(permSet, type, identifier) { + permSet.connectionPermissions = permSet.connectionPermissions || {}; return addObjectPermission(permSet.connectionPermissions, type, identifier); }; @@ -486,6 +496,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * present in the given permission set. */ PermissionSet.removeConnectionPermission = function removeConnectionPermission(permSet, type, identifier) { + permSet.connectionPermissions = permSet.connectionPermissions || {}; return removeObjectPermission(permSet.connectionPermissions, type, identifier); }; @@ -511,6 +522,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * already present in the given permission set. */ PermissionSet.addConnectionGroupPermission = function addConnectionGroupPermission(permSet, type, identifier) { + permSet.connectionGroupPermissions = permSet.connectionGroupPermissions || {}; return addObjectPermission(permSet.connectionGroupPermissions, type, identifier); }; @@ -535,6 +547,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * present in the given permission set. */ PermissionSet.removeConnectionGroupPermission = function removeConnectionGroupPermission(permSet, type, identifier) { + permSet.connectionGroupPermissions = permSet.connectionGroupPermissions || {}; return removeObjectPermission(permSet.connectionGroupPermissions, type, identifier); }; @@ -560,6 +573,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * already present in the given permission set. */ PermissionSet.addActiveConnectionPermission = function addActiveConnectionPermission(permSet, type, identifier) { + permSet.activeConnectionPermissions = permSet.activeConnectionPermissions || {}; return addObjectPermission(permSet.activeConnectionPermissions, type, identifier); }; @@ -584,6 +598,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * present in the given permission set. */ PermissionSet.removeActiveConnectionPermission = function removeActiveConnectionPermission(permSet, type, identifier) { + permSet.activeConnectionPermissions = permSet.activeConnectionPermissions || {}; return removeObjectPermission(permSet.activeConnectionPermissions, type, identifier); }; @@ -607,6 +622,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * already present in the given permission set. */ PermissionSet.addUserPermission = function addUserPermission(permSet, type, identifier) { + permSet.userPermissions = permSet.userPermissions || {}; return addObjectPermission(permSet.userPermissions, type, identifier); }; @@ -630,6 +646,7 @@ angular.module('rest').factory('PermissionSet', [function definePermissionSet() * present in the given permission set. */ PermissionSet.removeUserPermission = function removeUserPermission(permSet, type, identifier) { + permSet.userPermissions = permSet.userPermissions || {}; return removeObjectPermission(permSet.userPermissions, type, identifier); }; From 49c40c0b773fa4232321c7b29a088f9ebaa174c5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 14:06:15 -0700 Subject: [PATCH 08/14] GUAC-586: Restore redirection to home if permission to manage connections is denied. --- .../directives/guacSettingsConnections.js | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index fe0449fe2..701b52808 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -41,6 +41,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe var PermissionSet = $injector.get('PermissionSet'); // Required services + var $location = $injector.get('$location'); var $routeParams = $injector.get('$routeParams'); var authenticationService = $injector.get('authenticationService'); var connectionGroupService = $injector.get('connectionGroupService'); @@ -81,31 +82,6 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe */ $scope.rootGroups = null; - /** - * Whether the current user can manage connections. If the current - * permissions have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canManageConnections = null; - - /** - * Whether the current user can create new connections. If the - * current permissions have not yet been loaded, this will be null. - * - * @type Boolean - */ - $scope.canCreateConnections = null; - - /** - * Whether the current user can create new connection groups. If - * the current permissions have not yet been loaded, this will be - * null. - * - * @type Boolean - */ - $scope.canCreateConnectionGroups = null; - /** * All permissions associated with the current user, or null if the * user's permissions have not yet been loaded. @@ -214,9 +190,6 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe if ($scope.canCreateConnections() || $scope.canCreateConnectionGroups()) return true; - // Ignore permission to update root group - PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); - // For each data source for (var dataSource in $scope.permissions) { @@ -247,7 +220,17 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe currentUsername ) .then(function permissionsRetrieved(permissions) { + + // Store retrieved permissions $scope.permissions = permissions; + + // Ignore permission to update root group + PermissionSet.removeConnectionGroupPermission($scope.permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); + + // Return to home if there's nothing to do here + if (!$scope.canManageConnections()) + $location.path('/'); + }); // Retrieve all connections for which we have UPDATE or DELETE permission From 404909f37cf24339fdb81e29857cf6b3d227cece Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 14:15:09 -0700 Subject: [PATCH 09/14] GUAC-586: Clean up styling of read-only account notice. Do not show buttons on such an account. --- .../main/webapp/app/manage/styles/manage-user.css | 11 +++++++++++ .../main/webapp/app/manage/templates/manageUser.html | 12 ++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/guacamole/src/main/webapp/app/manage/styles/manage-user.css b/guacamole/src/main/webapp/app/manage/styles/manage-user.css index ab64e78b7..3445b6d5c 100644 --- a/guacamole/src/main/webapp/app/manage/styles/manage-user.css +++ b/guacamole/src/main/webapp/app/manage/styles/manage-user.css @@ -69,3 +69,14 @@ .manage-user .page-tabs .page-list li.linked a[href]:before { background-image: url('images/checkmark.png'); } + +.manage-user .notice.read-only { + + background: #FDA; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25em; + + text-align: center; + padding: 1em; + +} \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index 71a6d93cf..ab93683f6 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -91,13 +91,13 @@ THE SOFTWARE. - + +
+ + + +
- -
- - -
From 9ec3ddf3571613bfbe848541a4d6cab7fb879c02 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 15:27:52 -0700 Subject: [PATCH 10/14] GUAC-586: Ensure page list tabs appear in deterministic order. --- .../app/navigation/directives/guacPageList.js | 2 +- .../app/navigation/services/userPageService.js | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js b/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js index ef2b05e61..cc4d8fb13 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacPageList.js @@ -138,7 +138,7 @@ angular.module('navigation').directive('guacPageList', [function guacPageList() name : name, url : isCurrentPage ? currentURL : page.url, className : page.className, - weight : page.weight || weight + weight : page.weight || (weight + i) }); } diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 782f3fce7..257f4112a 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -53,6 +53,14 @@ angular.module('navigation').factory('userPageService', ['$injector', url : '/' }); + /** + * The identifiers of all data sources currently available to the + * authenticated user. + * + * @type String[] + */ + var dataSources = authenticationService.getAvailableDataSources(); + /** * Returns an appropriate home page for the current user. * @@ -176,9 +184,10 @@ angular.module('navigation').factory('userPageService', ['$injector', var canManageSessions = []; // Inspect the contents of each provided permission set - angular.forEach(permissionSets, function inspectPermissions(permissions, dataSource) { + angular.forEach(dataSources, function inspectPermissions(dataSource) { - permissions = angular.copy(permissions); + // Copy permissions for current data source + var permissions = angular.copy(permissionSets[dataSource]); // Ignore permission to update root group PermissionSet.removeConnectionGroupPermission(permissions, From 7a47064cd11ac6fb0511cc4bec51beda328e089e Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 15:42:09 -0700 Subject: [PATCH 11/14] GUAC-586: Do not inspect permissions if permission set does not exist. --- .../webapp/app/navigation/services/userPageService.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 257f4112a..d42b0381a 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -186,8 +186,13 @@ angular.module('navigation').factory('userPageService', ['$injector', // Inspect the contents of each provided permission set angular.forEach(dataSources, function inspectPermissions(dataSource) { - // Copy permissions for current data source - var permissions = angular.copy(permissionSets[dataSource]); + // Get permissions for current data source, skipping if non-existent + var permissions = permissionSets[dataSource]; + if (!permissions) + return; + + // Do not modify original object + permissions = angular.copy(permissions); // Ignore permission to update root group PermissionSet.removeConnectionGroupPermission(permissions, From df34b87460213547d34e310d4f5af41ba15d2116 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 15:44:03 -0700 Subject: [PATCH 12/14] GUAC-586: Simplify and fix connection permission checks in management UI. --- .../directives/guacSettingsConnections.js | 80 ++++++------------- .../templates/settingsConnections.html | 4 +- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index 701b52808..d088cbc31 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -86,7 +86,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe * All permissions associated with the current user, or null if the * user's permissions have not yet been loaded. * - * @type Object. + * @type PermissionSet */ $scope.permissions = null; @@ -106,11 +106,11 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe /** * Returns whether the current user can create new connections - * within at least one data source. + * within the current data source. * * @return {Boolean} * true if the current user can create new connections within - * at least one data source, false otherwise. + * the current data source, false otherwise. */ $scope.canCreateConnections = function canCreateConnections() { @@ -118,18 +118,10 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe if (!$scope.permissions) return false; - // For each data source - for (var dataSource in $scope.permissions) { - - // Retrieve corresponding permission set - var permissionSet = $scope.permissions[dataSource]; - - // Can create connections if adminstrator or have explicit permission - if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_CONNECTION)) - return true; - - } + // Can create connections if adminstrator or have explicit permission + if (PermissionSet.hasSystemPermission($scope.permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission($scope.permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION)) + return true; // No data sources allow connection creation return false; @@ -138,11 +130,11 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe /** * Returns whether the current user can create new connection - * groups within at least one data source. + * groups within the current data source. * * @return {Boolean} * true if the current user can create new connection groups - * within at least one data source, false otherwise. + * within the current data source, false otherwise. */ $scope.canCreateConnectionGroups = function canCreateConnectionGroups() { @@ -150,18 +142,10 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe if (!$scope.permissions) return false; - // For each data source - for (var dataSource in $scope.permissions) { - - // Retrieve corresponding permission set - var permissionSet = $scope.permissions[dataSource]; - - // Can create connections groups if adminstrator or have explicit permission - if (PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissionSet, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP)) - return true; - - } + // Can create connections groups if adminstrator or have explicit permission + if (PermissionSet.hasSystemPermission($scope.permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission($scope.permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP)) + return true; // No data sources allow connection group creation return false; @@ -171,14 +155,14 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe /** * Returns whether the current user can create new connections or * connection groups or make changes to existing connections or - * connection groups within at least one data source. The + * connection groups within the current data source. The * connection management interface as a whole is useless if this * function returns false. * * @return {Boolean} * true if the current user can create new connections/groups - * or make changes to existing connections/groups within at - * least one data source, false otherwise. + * or make changes to existing connections/groups within the + * current data source, false otherwise. */ $scope.canManageConnections = function canManageConnections() { @@ -190,23 +174,15 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe if ($scope.canCreateConnections() || $scope.canCreateConnectionGroups()) return true; - // For each data source - for (var dataSource in $scope.permissions) { + // Can manage connections if granted explicit update or delete + if (PermissionSet.hasConnectionPermission($scope.permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasConnectionPermission($scope.permissions, PermissionSet.ObjectPermissionType.DELETE)) + return true; - // Retrieve corresponding permission set - var permissionSet = $scope.permissions[dataSource]; - - // Can manage connections if granted explicit update or delete - if (PermissionSet.hasConnectionPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasConnectionPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE)) - return true; - - // Can manage connections groups if granted explicit update or delete - if (PermissionSet.hasConnectionGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasConnectionGroupPermission(permissionSet, PermissionSet.ObjectPermissionType.DELETE)) - return true; - - } + // Can manage connections groups if granted explicit update or delete + if (PermissionSet.hasConnectionGroupPermission($scope.permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasConnectionGroupPermission($scope.permissions, PermissionSet.ObjectPermissionType.DELETE)) + return true; // No data sources allow management of connections or groups return false; @@ -214,12 +190,8 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe }; // Retrieve current permissions - dataSourceService.apply( - permissionService.getPermissions, - [$scope.dataSource], - currentUsername - ) - .then(function permissionsRetrieved(permissions) { + permissionService.getPermissions($scope.dataSource, currentUsername) + .success(function permissionsRetrieved(permissions) { // Store retrieved permissions $scope.permissions = permissions; diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html index 0f9b618df..efecf6f8a 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsConnections.html @@ -28,11 +28,11 @@ From c82f2312b878269fd95e7ca88fcc6e31d0f5356d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 15:55:23 -0700 Subject: [PATCH 13/14] GUAC-586: Fix potential NullPointerExceptions in basic and LDAP auth. --- .../guacamole/auth/ldap/connection/ConnectionService.java | 6 ++++++ .../net/basic/BasicFileAuthenticationProvider.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java index 60629e959..aaa17edaa 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java @@ -35,6 +35,7 @@ import net.sourceforge.guacamole.net.auth.ldap.LDAPAuthenticationProvider; import org.glyptodon.guacamole.auth.ldap.ConfigurationService; import org.glyptodon.guacamole.auth.ldap.EscapingService; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleSecurityException; import org.glyptodon.guacamole.GuacamoleServerException; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.simple.SimpleConnection; @@ -91,6 +92,11 @@ public class ConnectionService { // Pull the current user DN from the LDAP connection String userDN = ldapConnection.getAuthenticationDN(); + // getConnections() will only be called after a connection has been + // authenticated (via non-anonymous bind), thus userDN cannot + // possibly be null + assert(userDN != null); + // Find all Guacamole connections for the given user LDAPSearchResults results = ldapConnection.search( confService.getConfigurationBaseDN(), diff --git a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java index e868a9117..ba3d441fe 100644 --- a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java +++ b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java @@ -206,7 +206,7 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide return null; // Validate and return info for given user and pass - Authorization auth = getUserMapping().getAuthorization(credentials.getUsername()); + Authorization auth = userMapping.getAuthorization(credentials.getUsername()); if (auth != null && auth.validate(credentials.getUsername(), credentials.getPassword())) return auth.getConfigurations(); From 606b5dc8f2428c814e12002b2b186467363e98e4 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 3 Sep 2015 16:21:19 -0700 Subject: [PATCH 14/14] GUAC-586: Remove unnecessary import. --- .../guacamole/auth/ldap/connection/ConnectionService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java index aaa17edaa..9a065fda2 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java @@ -35,7 +35,6 @@ import net.sourceforge.guacamole.net.auth.ldap.LDAPAuthenticationProvider; import org.glyptodon.guacamole.auth.ldap.ConfigurationService; import org.glyptodon.guacamole.auth.ldap.EscapingService; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleSecurityException; import org.glyptodon.guacamole.GuacamoleServerException; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.simple.SimpleConnection;