diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 1104741ab..b58481847 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -350,7 +350,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i * Cancels all pending edits, returning to the management page. */ $scope.cancel = function cancel() { - $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); + $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); }; /** @@ -372,7 +372,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i // Save the connection connectionService.saveConnection($scope.selectedDataSource, $scope.connection) .success(function savedConnection() { - $location.path('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); + $location.url('/settings/' + encodeURIComponent($scope.selectedDataSource) + '/connections'); }) // Notify of any errors diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 1641091fa..125d41964 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -80,6 +80,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto */ var selectedDataSource = $routeParams.dataSource; + /** + * The username of the original user from which this user is + * being cloned. Only valid if this is a new user. + * + * @type String + */ + var cloneSourceUsername = $location.search().clone; + /** * The username of the user being edited. If a new user is * being created, this will not be defined. @@ -88,6 +96,18 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto */ var username = $routeParams.id; + /** + * The string value representing the user currently being edited within the + * permission flag set. Note that his may not match the user's actual + * username - it is a marker that is (1) guaranteed to be associated with + * the current user's permissions in the permission set and (2) guaranteed + * not to collide with any user that does not represent the current user + * within the permission set. + * + * @type String + */ + $scope.selfUsername = ''; + /** * 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 @@ -362,6 +382,42 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }; + /** + * Returns whether the current user can clone 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 clone the user being edited, false + * otherwise. + */ + $scope.canCloneUser = function canCloneUser(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; + + // If we are not editing an existing user, we cannot clone + if (!username) + return false; + + // The administrator can always clone users + if (PermissionSet.hasSystemPermission($scope.permissions[dataSource], + PermissionSet.SystemPermissionType.ADMINISTER)) + return true; + + // Otherwise we need explicit CREATE_USER permission + return PermissionSet.hasSystemPermission($scope.permissions[dataSource], + PermissionSet.SystemPermissionType.CREATE_USER); + + }; + /** * Returns whether the current user can delete the user being edited from * the given data source. @@ -477,6 +533,10 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); + // The current user will be associated with username of the existing + // user in the retrieved permission set + $scope.selfUsername = username; + // Pull user permissions permissionService.getPermissions(selectedDataSource, username).success(function gotPermissions(permissions) { $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions); @@ -488,6 +548,35 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); } + // If we are cloning an existing user, pull his/her data instead + else if (cloneSourceUsername) { + + dataSourceService.apply(userService.getUser, dataSources, cloneSourceUsername) + .then(function usersReceived(users) { + + // Get user for currently-selected data source + $scope.users = {}; + $scope.user = users[selectedDataSource]; + + }); + + // The current user will be associated with cloneSourceUsername in the + // retrieved permission set + $scope.selfUsername = cloneSourceUsername; + + // Pull user permissions + permissionService.getPermissions(selectedDataSource, cloneSourceUsername) + .success(function gotPermissions(permissions) { + $scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions); + permissionsAdded = permissions; + }) + + // If permissions cannot be retrieved, use empty permissions + .error(function permissionRetrievalFailed() { + $scope.permissionFlags = new PermissionFlagSet(); + }); + } + // Use skeleton data if we are creating a new user else { @@ -836,7 +925,15 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto * Cancels all pending edits, returning to the management page. */ $scope.cancel = function cancel() { - $location.path('/settings/users'); + $location.url('/settings/users'); + }; + + /** + * Cancels all pending edits, opening an edit page for a new user + * which is prepopulated with the data from the user currently being edited. + */ + $scope.cloneUser = function cloneUser() { + $location.path('/manage/' + encodeURIComponent(selectedDataSource) + '/users').search('clone', username); }; /** @@ -864,10 +961,27 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto saveUserPromise.success(function savedUser() { + // Move permission flags if username differs from marker + if ($scope.selfUsername !== $scope.user.username) { + + // Rename added permission + if (permissionsAdded.userPermissions[$scope.selfUsername]) { + permissionsAdded.userPermissions[$scope.user.username] = permissionsAdded.userPermissions[$scope.selfUsername]; + delete permissionsAdded.userPermissions[$scope.selfUsername]; + } + + // Rename removed permission + if (permissionsRemoved.userPermissions[$scope.selfUsername]) { + permissionsRemoved.userPermissions[$scope.user.username] = permissionsRemoved.userPermissions[$scope.selfUsername]; + delete permissionsRemoved.userPermissions[$scope.selfUsername]; + } + + } + // Upon success, save any changed permissions permissionService.patchPermissions(selectedDataSource, $scope.user.username, permissionsAdded, permissionsRemoved) .success(function patchedUserPermissions() { - $location.path('/settings/users'); + $location.url('/settings/users'); }) // Notify of any errors diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index 6d275e5c1..858665463 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -78,8 +78,8 @@ THE SOFTWARE. {{'MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD' | translate}} - + @@ -108,6 +108,7 @@ THE SOFTWARE.
+
diff --git a/guacamole/src/main/webapp/translations/de.json b/guacamole/src/main/webapp/translations/de.json index 996f6adbb..058193835 100644 --- a/guacamole/src/main/webapp/translations/de.json +++ b/guacamole/src/main/webapp/translations/de.json @@ -237,6 +237,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 951b38b03..c311f7eb2 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -237,6 +237,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", diff --git a/guacamole/src/main/webapp/translations/fr.json b/guacamole/src/main/webapp/translations/fr.json index c8fa6be22..f391bfe5e 100644 --- a/guacamole/src/main/webapp/translations/fr.json +++ b/guacamole/src/main/webapp/translations/fr.json @@ -228,6 +228,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", diff --git a/guacamole/src/main/webapp/translations/it.json b/guacamole/src/main/webapp/translations/it.json index 0dea25c3f..bed8c4c8c 100644 --- a/guacamole/src/main/webapp/translations/it.json +++ b/guacamole/src/main/webapp/translations/it.json @@ -225,6 +225,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", diff --git a/guacamole/src/main/webapp/translations/nl.json b/guacamole/src/main/webapp/translations/nl.json index 4dbdd8c47..e4c9a8111 100644 --- a/guacamole/src/main/webapp/translations/nl.json +++ b/guacamole/src/main/webapp/translations/nl.json @@ -237,6 +237,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", diff --git a/guacamole/src/main/webapp/translations/ru.json b/guacamole/src/main/webapp/translations/ru.json index 6ac7fbe0c..9fdd48fb9 100644 --- a/guacamole/src/main/webapp/translations/ru.json +++ b/guacamole/src/main/webapp/translations/ru.json @@ -225,6 +225,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE",