From 9a8aa1467441e068b488ea544acca65321dc65d3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 11 Mar 2015 17:58:31 -0700 Subject: [PATCH] GUAC-1120: Move logout panel to common user menu. --- .../app/home/controllers/homeController.js | 174 +----------- .../src/main/webapp/app/home/homeModule.js | 2 +- .../src/main/webapp/app/home/styles/home.css | 39 --- .../main/webapp/app/home/templates/home.html | 33 +-- .../app/index/controllers/indexController.js | 13 +- .../src/main/webapp/app/index/styles/ui.css | 6 - .../controllers/manageConnectionController.js | 23 +- .../manageConnectionGroupController.js | 11 + .../manage/controllers/manageController.js | 11 + .../controllers/manageUserController.js | 13 +- .../main/webapp/app/manage/manageModule.js | 2 +- .../webapp/app/manage/templates/manage.html | 6 +- .../manage/templates/manageConnection.html | 6 +- .../templates/manageConnectionGroup.html | 6 +- .../app/manage/templates/manageUser.html | 6 +- .../app/userMenu/directives/guacUserMenu.js | 250 ++++++++++++++++++ .../webapp/app/userMenu/styles/user-menu.css | 60 +++++ .../app/userMenu/templates/guacUserMenu.html | 55 ++++ .../webapp/app/userMenu/userMenuModule.js | 27 ++ .../src/main/webapp/translations/en_US.json | 47 ++-- 20 files changed, 487 insertions(+), 303 deletions(-) create mode 100644 guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js create mode 100644 guacamole/src/main/webapp/app/userMenu/styles/user-menu.css create mode 100644 guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html create mode 100644 guacamole/src/main/webapp/app/userMenu/userMenuModule.js diff --git a/guacamole/src/main/webapp/app/home/controllers/homeController.js b/guacamole/src/main/webapp/app/home/controllers/homeController.js index 601b18ae5..a50763302 100644 --- a/guacamole/src/main/webapp/app/home/controllers/homeController.js +++ b/guacamole/src/main/webapp/app/home/controllers/homeController.js @@ -28,13 +28,11 @@ angular.module('home').controller('homeController', ['$scope', '$injector', // Get required types var ConnectionGroup = $injector.get("ConnectionGroup"); - var PermissionSet = $injector.get("PermissionSet"); // Get required services var authenticationService = $injector.get("authenticationService"); var connectionGroupService = $injector.get("connectionGroupService"); var permissionService = $injector.get("permissionService"); - var userService = $injector.get("userService"); /** * The root connection group, or null if the connection group hierarchy has @@ -45,44 +43,12 @@ angular.module('home').controller('homeController', ['$scope', '$injector', $scope.rootConnectionGroup = null; /** - * Whether the current user has sufficient permissions to use the - * management interface. If permissions have not yet been loaded, this will - * be null. + * All permissions associated with the current user, or null if the user's + * permissions have not yet been loaded. * - * @type Boolean + * @type PermissionSet */ - $scope.canManageGuacamole = null; - - /** - * Whether the current user has sufficient permissions to change - * his/her own password. If permissions have not yet been loaded, this will - * be null. - * - * @type Boolean - */ - $scope.canChangePassword = null; - - /** - * Whether the password edit dialog should be shown. - * - * @type Boolean - */ - $scope.showPasswordDialog = false; - - /** - * The new password for the user. - * - * @type String - */ - $scope.newPassword = null; - - /** - * The password match for the user. The update password action will fail if - * $scope.newPassword !== $scope.passwordMatch. - * - * @type String - */ - $scope.newPasswordMatch = null; + $scope.permissions = null; /** * Returns whether critical data has completed being loaded. @@ -94,8 +60,7 @@ angular.module('home').controller('homeController', ['$scope', '$injector', $scope.isLoaded = function isLoaded() { return $scope.rootConnectionGroup !== null - && $scope.canManageGuacamole !== null - && $scope.canChangePassword !== null; + && $scope.permissions !== null; }; @@ -105,135 +70,10 @@ angular.module('home').controller('homeController', ['$scope', '$injector', $scope.rootConnectionGroup = rootConnectionGroup; }); - // Identifier of the current user - var currentUserID = authenticationService.getCurrentUserID(); - // Retrieve current permissions - permissionService.getPermissions(currentUserID) + permissionService.getPermissions(authenticationService.getCurrentUserID()) .success(function permissionsRetrieved(permissions) { - - // Determine whether the current user can change his/her own password - $scope.canChangePassword = - PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, currentUserID) - && PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.READ, currentUserID); - - // Ignore permission to update root group - PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); - - // Ignore permission to update self - PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, currentUserID); - - // Determine whether the current user needs access to the management UI - $scope.canManageGuacamole = - - // System permissions - PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION) - || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP) - - // Permission to update objects - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) - - // Permission to delete objects - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) - - // Permission to administer objects - || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) - || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) - || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); - + $scope.permissions = permissions; }); - /** - * An action to be provided along with the object sent to showStatus which - * closes the currently-shown status dialog. - */ - var ACKNOWLEDGE_ACTION = { - name : "HOME.ACTION_ACKNOWLEDGE", - // Handle action - callback : function acknowledgeCallback() { - $scope.showStatus(false); - } - }; - - /** - * Show the password update dialog. - */ - $scope.showPasswordUpdate = function showPasswordUpdate() { - - // Show the dialog - $scope.showPasswordDialog = true; - }; - - /** - * Close the password update dialog. - */ - $scope.closePasswordUpdate = function closePasswordUpdate() { - - // Clear the password fields and close the dialog - $scope.oldPassword = null; - $scope.newPassword = null; - $scope.newPasswordMatch = null; - $scope.showPasswordDialog = false; - }; - - /** - * Update the current user's password to the password currently set within - * the password change dialog. - */ - $scope.updatePassword = function updatePassword() { - - // Verify passwords match - if ($scope.newPasswordMatch !== $scope.newPassword) { - $scope.showStatus({ - className : 'error', - title : 'HOME.DIALOG_HEADER_ERROR', - text : 'HOME.ERROR_PASSWORD_MISMATCH', - actions : [ ACKNOWLEDGE_ACTION ] - }); - return; - } - - // Verify that the new password is not blank - if (!$scope.newPassword) { - $scope.showStatus({ - className : 'error', - title : 'HOME.DIALOG_HEADER_ERROR', - text : 'HOME.ERROR_PASSWORD_BLANK', - actions : [ ACKNOWLEDGE_ACTION ] - }); - return; - } - - // Save the user with the new password - userService.updateUserPassword(currentUserID, $scope.oldPassword, $scope.newPassword) - .success(function passwordUpdated() { - - // Close the password update dialog - $scope.closePasswordUpdate(); - - // Indicate that the password has been changed - $scope.showStatus({ - text : 'HOME.PASSWORD_CHANGED', - actions : [ ACKNOWLEDGE_ACTION ] - }); - }) - - // Notify of any errors - .error(function passwordUpdateFailed(error) { - $scope.showStatus({ - className : 'error', - title : 'HOME.DIALOG_HEADER_ERROR', - 'text' : error.message, - actions : [ ACKNOWLEDGE_ACTION ] - }); - }); - - }; - }]); diff --git a/guacamole/src/main/webapp/app/home/homeModule.js b/guacamole/src/main/webapp/app/home/homeModule.js index 146ad4ced..97dbbcc97 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', ['client', 'history', 'groupList', 'rest']); +angular.module('home', ['client', 'history', 'groupList', 'rest', 'userMenu']); diff --git a/guacamole/src/main/webapp/app/home/styles/home.css b/guacamole/src/main/webapp/app/home/styles/home.css index 9754d74af..4aca95213 100644 --- a/guacamole/src/main/webapp/app/home/styles/home.css +++ b/guacamole/src/main/webapp/app/home/styles/home.css @@ -20,12 +20,6 @@ * THE SOFTWARE. */ -div.logout-panel { - padding: 0.45em; - text-align: right; - float: right; -} - .history-unavailable div.recent-connections { display: none; } @@ -69,36 +63,3 @@ div.recent-connections div.connection { max-width: 75%; overflow: hidden; } - -.password-dialog { - visibility: hidden; - opacity: 0; - -webkit-transition: visibility 0.125s, opacity 0.125s; - -moz-transition: visibility 0.125s, opacity 0.125s; - -ms-transition: visibility 0.125s, opacity 0.125s; - -o-transition: visibility 0.125s, opacity 0.125s; - transition: visibility 0.125s, opacity 0.125s; - position: absolute; - background: white; - padding: 1em; - border: 1px solid rgba(0, 0, 0, 0.25); - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); - margin: 1em; - right: 0; - top: 0; - z-index: 1; -} - -.password-dialog.shown { - visibility: visible; - opacity: 1; - -webkit-transition: opacity 0.125s; - -moz-transition: opacity 0.125s; - -ms-transition: opacity 0.125s; - -o-transition: opacity 0.125s; - transition: opacity 0.125s; -} - -.password-dialog .fields { - width: 100%; -} \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/home/templates/home.html b/guacamole/src/main/webapp/app/home/templates/home.html index 2fb88ed51..6dc4f6a74 100644 --- a/guacamole/src/main/webapp/app/home/templates/home.html +++ b/guacamole/src/main/webapp/app/home/templates/home.html @@ -24,37 +24,8 @@
-
- {{'HOME.ACTION_CHANGE_PASSWORD' | translate}} -
- -
- - - - - - - - - - - - - -
{{'HOME.FIELD_HEADER_PASSWORD_OLD' | translate}}
{{'HOME.FIELD_HEADER_PASSWORD_NEW' | translate}}
{{'HOME.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}
-
- - -
- - -
-
- - {{'HOME.ACTION_MANAGE' | translate}} - {{'HOME.ACTION_LOGOUT' | translate}} -
+ +

{{'HOME.SECTION_HEADER_RECENT_CONNECTIONS' | translate}}

diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 3e7f1e733..59e657423 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -27,10 +27,8 @@ angular.module('index').controller('indexController', ['$scope', '$injector', function indexController($scope, $injector) { // Required services - var $document = $injector.get("$document"); - var $location = $injector.get("$location"); - var $window = $injector.get("$window"); - var authenticationService = $injector.get("authenticationService"); + var $document = $injector.get("$document"); + var $window = $injector.get("$window"); /** * The current status notification, or false if no status is currently @@ -153,13 +151,6 @@ angular.module('index').controller('indexController', ['$scope', '$injector', } }; - // Provide simple mechanism for logging out the current user - $scope.logout = function logout() { - authenticationService.logout()['finally'](function logoutComplete() { - $location.path('/login'); - }); - }; - // Create event listeners at the global level var keyboard = new Guacamole.Keyboard($document[0]); diff --git a/guacamole/src/main/webapp/app/index/styles/ui.css b/guacamole/src/main/webapp/app/index/styles/ui.css index f17797341..1be5a3a96 100644 --- a/guacamole/src/main/webapp/app/index/styles/ui.css +++ b/guacamole/src/main/webapp/app/index/styles/ui.css @@ -289,12 +289,6 @@ div.section { background-image: url('images/group-icons/guac-open.png'); } -div.logout-panel { - padding: 0.45em; - text-align: right; - float: right; -} - .history th, .history td { padding-left: 1em; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 13f59dd54..b414aa250 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -128,6 +128,14 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i */ $scope.canCloneConnection = null; + /** + * All permissions associated with the current user, or null if the user's + * permissions have not yet been loaded. + * + * @type PermissionSet + */ + $scope.permissions = null; + /** * Returns whether critical data has completed being loaded. * @@ -142,6 +150,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i && $scope.connection !== null && $scope.parameters !== null && $scope.historyEntryWrappers !== null + && $scope.permissions !== null && $scope.canSaveConnection !== null && $scope.canDeleteConnection !== null && $scope.canCloneConnection !== null; @@ -159,21 +168,23 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i permissionService.getPermissions(authenticationService.getCurrentUserID()) .success(function permissionsReceived(permissions) { - // Check if the connection is new or if the user has UPDATE permission - $scope.canSaveConnection = + $scope.permissions = permissions; + + // Check if the connection is new or if the user has UPDATE permission + $scope.canSaveConnection = !identifier || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier); - // Check if connection is not new and the user has DELETE permission - $scope.canDeleteConnection = + // Check if connection is not new and the user has DELETE permission + $scope.canDeleteConnection = !!identifier && ( PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE, identifier) ); - // Check if the connection is not new and the user has UPDATE and CREATE_CONNECTION permissions - $scope.canCloneConnection = + // Check if the connection is not new and the user has UPDATE and CREATE_CONNECTION permissions + $scope.canCloneConnection = !!identifier && ( PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) || ( PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, identifier) diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index 94813b8d5..dfd8c5f40 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -85,6 +85,14 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' */ $scope.hasDeletePermission = null; + /** + * All permissions associated with the current user, or null if the user's + * permissions have not yet been loaded. + * + * @type PermissionSet + */ + $scope.permissions = null; + /** * Returns whether critical data has completed being loaded. * @@ -96,6 +104,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' return $scope.rootGroup !== null && $scope.connectionGroup !== null + && $scope.permissions !== null && $scope.canSaveConnectionGroup !== null && $scope.canDeleteConnectionGroup !== null; @@ -105,6 +114,8 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' permissionService.getPermissions(authenticationService.getCurrentUserID()) .success(function permissionsReceived(permissions) { + $scope.permissions = permissions; + // Check if the connection group is new or if the user has UPDATE permission $scope.canSaveConnectionGroup = !identifier diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageController.js b/guacamole/src/main/webapp/app/manage/controllers/manageController.js index f757234c5..36ba0983a 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageController.js @@ -115,6 +115,14 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', */ $scope.newUsername = ""; + /** + * All permissions associated with the current user, or null if the user's + * permissions have not yet been loaded. + * + * @type PermissionSet + */ + $scope.permissions = null; + /** * Returns whether critical data has completed being loaded. * @@ -126,6 +134,7 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', return $scope.users !== null && $scope.rootGroup !== null + && $scope.permissions !== null && $scope.canManageUsers !== null && $scope.canManageConnections !== null && $scope.canCreateUsers !== null @@ -138,6 +147,8 @@ angular.module('manage').controller('manageController', ['$scope', '$injector', permissionService.getPermissions(currentUserID) .success(function permissionsRetrieved(permissions) { + $scope.permissions = permissions; + // Ignore permission to update root group PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index 9aa16627a..d7619f798 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -93,6 +93,14 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto */ $scope.hasDeletePermission = null; + /** + * All permissions associated with the current user, or null if the user's + * permissions have not yet been loaded. + * + * @type PermissionSet + */ + $scope.permissions = null; + /** * Returns whether critical data has completed being loaded. * @@ -105,6 +113,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto return $scope.user !== null && $scope.permissionFlags !== null && $scope.rootGroup !== null + && $scope.permissions !== null && $scope.canSaveUser !== null && $scope.canDeleteUser !== null; @@ -129,7 +138,9 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto // Query the user's permissions for the current connection permissionService.getPermissions(authenticationService.getCurrentUserID()) .success(function permissionsReceived(permissions) { - + + $scope.permissions = permissions; + // Check if the user is new or if the user has UPDATE permission $scope.canSaveUser = !username diff --git a/guacamole/src/main/webapp/app/manage/manageModule.js b/guacamole/src/main/webapp/app/manage/manageModule.js index e32aa54d1..d6261ada6 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', ['groupList', 'locale', 'pager', 'rest']); +angular.module('manage', ['groupList', 'locale', 'pager', 'rest', 'userMenu']); diff --git a/guacamole/src/main/webapp/app/manage/templates/manage.html b/guacamole/src/main/webapp/app/manage/templates/manage.html index b5834a33a..483da6d4f 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manage.html +++ b/guacamole/src/main/webapp/app/manage/templates/manage.html @@ -22,10 +22,8 @@ THE SOFTWARE.
- + +

{{'MANAGE.SECTION_HEADER_ADMINISTRATION' | translate}}

diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html index 78bd187bc..723c29c89 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnection.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnection.html @@ -22,10 +22,8 @@ THE SOFTWARE.
- + +

{{'MANAGE_CONNECTION.SECTION_HEADER_EDIT_CONNECTION' | translate}}

diff --git a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html index 81b4b78ee..2e5fe1257 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageConnectionGroup.html @@ -22,10 +22,8 @@ THE SOFTWARE.
- + +

{{'MANAGE_CONNECTION_GROUP.SECTION_HEADER_EDIT_CONNECTION_GROUP' | translate}}

diff --git a/guacamole/src/main/webapp/app/manage/templates/manageUser.html b/guacamole/src/main/webapp/app/manage/templates/manageUser.html index 7f9112d3e..a066249f0 100644 --- a/guacamole/src/main/webapp/app/manage/templates/manageUser.html +++ b/guacamole/src/main/webapp/app/manage/templates/manageUser.html @@ -22,10 +22,8 @@ THE SOFTWARE.
- + +

{{'MANAGE_USER.SECTION_HEADER_EDIT_USER' | translate}}

diff --git a/guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js new file mode 100644 index 000000000..fc2095ec5 --- /dev/null +++ b/guacamole/src/main/webapp/app/userMenu/directives/guacUserMenu.js @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2015 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 provides a user-oriented menu containing options for + * navigation and configuration. + */ +angular.module('userMenu').directive('guacUserMenu', [function guacUserMenu() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The permissions associated with the user for whom this menu is + * being displayed. + * + * @type PermissionSet + */ + permissions : '=' + + }, + + templateUrl: 'app/userMenu/templates/guacUserMenu.html', + controller: ['$scope', '$injector', function guacUserMenuController($scope, $injector) { + + // Get required types + var ConnectionGroup = $injector.get("ConnectionGroup"); + var PermissionSet = $injector.get("PermissionSet"); + + // Get required services + var $location = $injector.get("$location"); + var authenticationService = $injector.get("authenticationService"); + var userService = $injector.get("userService"); + + /** + * An action to be provided along with the object sent to + * showStatus which closes the currently-shown status dialog. + */ + var ACKNOWLEDGE_ACTION = { + name : "HOME.ACTION_ACKNOWLEDGE", + // Handle action + callback : function acknowledgeCallback() { + $scope.showStatus(false); + } + }; + + /** + * Whether the current user has sufficient permissions to use the + * management interface. If permissions have not yet been loaded, + * this will be null. + * + * @type Boolean + */ + $scope.canManageGuacamole = null; + + /** + * Whether the current user has sufficient permissions to change + * his/her own password. If permissions have not yet been loaded, + * this will be null. + * + * @type Boolean + */ + $scope.canChangePassword = null; + + /** + * Whether the password edit dialog should be shown. + * + * @type Boolean + */ + $scope.showPasswordDialog = false; + + /** + * The new password for the user. + * + * @type String + */ + $scope.newPassword = null; + + /** + * The password match for the user. The update password action will + * fail if $scope.newPassword !== $scope.passwordMatch. + * + * @type String + */ + $scope.newPasswordMatch = null; + + /** + * The username of the current user. + * + * @type String + */ + $scope.username = authenticationService.getCurrentUserID(); + + // Update available menu options when permissions are changed + $scope.$watch('permissions', function permissionsChanged(permissions) { + + // Permissions are unknown if no permissions are provided + if (!permissions) { + $scope.canChangePassword = null; + $scope.canManageGuacamole = null; + return; + } + + // Determine whether the current user can change his/her own password + $scope.canChangePassword = + PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, $scope.username) + && PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.READ, $scope.username); + + // Ignore permission to update root group + PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); + + // Ignore permission to update self + PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, $scope.username); + + // Determine whether the current user needs access to the management UI + $scope.canManageGuacamole = + + // System permissions + PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.ADMINISTER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_USER) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION) + || PermissionSet.hasSystemPermission(permissions, PermissionSet.SystemPermissionType.CREATE_CONNECTION_GROUP) + + // Permission to update objects + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE) + + // Permission to delete objects + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.DELETE) + + // Permission to administer objects + || PermissionSet.hasConnectionPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) + || PermissionSet.hasConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER) + || PermissionSet.hasUserPermission(permissions, PermissionSet.ObjectPermissionType.ADMINISTER); + + }); + + /** + * Show the password update dialog. + */ + $scope.showPasswordUpdate = function showPasswordUpdate() { + + // Show the dialog + $scope.showPasswordDialog = true; + }; + + /** + * Close the password update dialog. + */ + $scope.closePasswordUpdate = function closePasswordUpdate() { + + // Clear the password fields and close the dialog + $scope.oldPassword = null; + $scope.newPassword = null; + $scope.newPasswordMatch = null; + $scope.showPasswordDialog = false; + }; + + /** + * Update the current user's password to the password currently set within + * the password change dialog. + */ + $scope.updatePassword = function updatePassword() { + + // Verify passwords match + if ($scope.newPasswordMatch !== $scope.newPassword) { + $scope.showStatus({ + className : 'error', + title : 'HOME.DIALOG_HEADER_ERROR', + text : 'HOME.ERROR_PASSWORD_MISMATCH', + actions : [ ACKNOWLEDGE_ACTION ] + }); + return; + } + + // Verify that the new password is not blank + if (!$scope.newPassword) { + $scope.showStatus({ + className : 'error', + title : 'HOME.DIALOG_HEADER_ERROR', + text : 'HOME.ERROR_PASSWORD_BLANK', + actions : [ ACKNOWLEDGE_ACTION ] + }); + return; + } + + // Save the user with the new password + userService.updateUserPassword($scope.username, $scope.oldPassword, $scope.newPassword) + .success(function passwordUpdated() { + + // Close the password update dialog + $scope.closePasswordUpdate(); + + // Indicate that the password has been changed + $scope.showStatus({ + text : 'HOME.PASSWORD_CHANGED', + actions : [ ACKNOWLEDGE_ACTION ] + }); + }) + + // Notify of any errors + .error(function passwordUpdateFailed(error) { + $scope.showStatus({ + className : 'error', + title : 'HOME.DIALOG_HEADER_ERROR', + 'text' : error.message, + actions : [ ACKNOWLEDGE_ACTION ] + }); + }); + + }; + + /** + * Logs out the current user, redirecting them to back to the login + * screen after logout completes. + */ + $scope.logout = function logout() { + authenticationService.logout()['finally'](function logoutComplete() { + $location.path('/login'); + }); + }; + + }] // end controller + + }; +}]); diff --git a/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css b/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css new file mode 100644 index 000000000..75e79b45a --- /dev/null +++ b/guacamole/src/main/webapp/app/userMenu/styles/user-menu.css @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 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. + */ + +.user-menu { + padding: 0.45em; + text-align: right; + float: right; +} + +.user-menu .password-dialog { + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 0.125s, opacity 0.125s; + -moz-transition: visibility 0.125s, opacity 0.125s; + -ms-transition: visibility 0.125s, opacity 0.125s; + -o-transition: visibility 0.125s, opacity 0.125s; + transition: visibility 0.125s, opacity 0.125s; + position: absolute; + background: white; + padding: 1em; + border: 1px solid rgba(0, 0, 0, 0.25); + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); + margin: 1em; + right: 0; + top: 0; + z-index: 1; +} + +.user-menu .password-dialog.shown { + visibility: visible; + opacity: 1; + -webkit-transition: opacity 0.125s; + -moz-transition: opacity 0.125s; + -ms-transition: opacity 0.125s; + -o-transition: opacity 0.125s; + transition: opacity 0.125s; +} + +.user-menu .password-dialog .fields { + width: 100%; +} diff --git a/guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html b/guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html new file mode 100644 index 000000000..0f9cdbad9 --- /dev/null +++ b/guacamole/src/main/webapp/app/userMenu/templates/guacUserMenu.html @@ -0,0 +1,55 @@ +
+ + + {{'USER_MENU.ACTION_CHANGE_PASSWORD' | translate}} +
+ +
+ + + + + + + + + + + + + +
{{'USER_MENU.FIELD_HEADER_PASSWORD_OLD' | translate}}
{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW' | translate}}
{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}
+
+ + +
+ + +
+
+ + {{'USER_MENU.ACTION_NAVIGATE_HOME' | translate}} + {{'USER_MENU.ACTION_MANAGE' | translate}} + {{'USER_MENU.ACTION_LOGOUT' | translate}} + +
diff --git a/guacamole/src/main/webapp/app/userMenu/userMenuModule.js b/guacamole/src/main/webapp/app/userMenu/userMenuModule.js new file mode 100644 index 000000000..b08453e51 --- /dev/null +++ b/guacamole/src/main/webapp/app/userMenu/userMenuModule.js @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 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 a user-oriented menu, containing the current username + * and options for navigation, changing the user's password, logging out, etc. + */ +angular.module('userMenu', []); diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index de751955e..7e31dc77a 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -114,22 +114,6 @@ "HOME" : { - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", - "ACTION_CHANGE_PASSWORD" : "@:APP.ACTION_CHANGE_PASSWORD", - "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", - "ACTION_MANAGE" : "@:APP.ACTION_MANAGE", - "ACTION_SAVE" : "@:APP.ACTION_SAVE", - - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - - "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", - "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", - - "FIELD_HEADER_PASSWORD_OLD" : "Current Password:", - "FIELD_HEADER_PASSWORD_NEW" : "New Password:", - "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Confirm New Password:", - "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", "INFO_NO_RECENT_CONNECTIONS" : "No recent connections.", @@ -155,8 +139,6 @@ "MANAGE" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", - "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", "ACTION_NEW_USER" : "New User", "ACTION_NEW_CONNECTION" : "New Connection", "ACTION_NEW_CONNECTION_GROUP" : "New Group", @@ -182,8 +164,6 @@ "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", - "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", - "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection", @@ -215,8 +195,6 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_DELETE" : "@:APP.ACTION_DELETE", - "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", - "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete Connection Group", @@ -240,8 +218,6 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", "ACTION_DELETE" : "@:APP.ACTION_DELETE", - "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", - "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_SAVE" : "@:APP.ACTION_SAVE", "DIALOG_HEADER_CONFIRM_DELETE" : "Delete User", @@ -403,6 +379,29 @@ "NAME" : "VNC" + }, + + "USER_MENU" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CHANGE_PASSWORD" : "@:APP.ACTION_CHANGE_PASSWORD", + "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", + "ACTION_MANAGE" : "@:APP.ACTION_MANAGE", + "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", + "ACTION_SAVE" : "@:APP.ACTION_SAVE", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + + "ERROR_PASSWORD_BLANK" : "Your password cannot be blank.", + "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + + "FIELD_HEADER_PASSWORD_OLD" : "Current Password:", + "FIELD_HEADER_PASSWORD_NEW" : "New Password:", + "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Confirm New Password:", + + "PASSWORD_CHANGED" : "Password changed." + } }