GUAC-1120: Move logout panel to common user menu.

This commit is contained in:
Michael Jumper
2015-03-11 17:58:31 -07:00
parent 2c60282f31
commit 9a8aa14674
20 changed files with 487 additions and 303 deletions

View File

@@ -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 ]
});
});
};
}]);

View File

@@ -20,4 +20,4 @@
* THE SOFTWARE.
*/
angular.module('home', ['client', 'history', 'groupList', 'rest']);
angular.module('home', ['client', 'history', 'groupList', 'rest', 'userMenu']);

View File

@@ -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%;
}

View File

@@ -24,37 +24,8 @@
<div class="connection-list-ui">
<div class="logout-panel">
<a class="change-password button" ng-click="showPasswordUpdate()" ng-show="canChangePassword">{{'HOME.ACTION_CHANGE_PASSWORD' | translate}}</a>
<div class="password-dialog" ng-class="{shown: showPasswordDialog}">
<!-- Password editor -->
<div class="section">
<table class="fields">
<tr>
<th>{{'HOME.FIELD_HEADER_PASSWORD_OLD' | translate}}</th>
<td><input ng-model="oldPassword" type="password" /></td>
</tr>
<tr>
<th>{{'HOME.FIELD_HEADER_PASSWORD_NEW' | translate}}</th>
<td><input ng-model="newPassword" type="password" /></td>
</tr>
<tr>
<th>{{'HOME.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}</th>
<td><input ng-model="newPasswordMatch" type="password" /></td>
</tr>
</table>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-click="updatePassword()">{{'HOME.ACTION_SAVE' | translate}}</button>
<button ng-click="closePasswordUpdate()">{{'HOME.ACTION_CANCEL' | translate}}</button>
</div>
</div>
<a class="manage button" ng-show="canManageGuacamole" href="#/manage">{{'HOME.ACTION_MANAGE' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'HOME.ACTION_LOGOUT' | translate}}</a>
</div>
<!-- User menu -->
<guac-user-menu permissions="permissions"></guac-user-menu>
<!-- The recent connections for this user -->
<h2>{{'HOME.SECTION_HEADER_RECENT_CONNECTIONS' | translate}}</h2>

View File

@@ -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]);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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']);

View File

@@ -22,10 +22,8 @@ THE SOFTWARE.
<div class="view" ng-class="{loading: !isLoaded()}">
<div class="logout-panel">
<a class="home button" href="#/">{{'MANAGE.ACTION_NAVIGATE_HOME' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'MANAGE.ACTION_LOGOUT' | translate}}</a>
</div>
<!-- User menu -->
<guac-user-menu permissions="permissions"></guac-user-menu>
<h2>{{'MANAGE.SECTION_HEADER_ADMINISTRATION' | translate}}</h2>

View File

@@ -22,10 +22,8 @@ THE SOFTWARE.
<div class="view" ng-class="{loading: !isLoaded()}">
<div class="logout-panel">
<a class="back button" href="#/manage/">{{'MANAGE_CONNECTION.ACTION_NAVIGATE_BACK' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'MANAGE_CONNECTION.ACTION_LOGOUT' | translate}}</a>
</div>
<!-- User menu -->
<guac-user-menu permissions="permissions"></guac-user-menu>
<!-- Main property editor -->
<h2>{{'MANAGE_CONNECTION.SECTION_HEADER_EDIT_CONNECTION' | translate}}</h2>

View File

@@ -22,10 +22,8 @@ THE SOFTWARE.
<div class="view" ng-class="{loading: !isLoaded()}">
<div class="logout-panel">
<a class="back button" href="#/manage/">{{'MANAGE_CONNECTION_GROUP.ACTION_NAVIGATE_BACK' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'MANAGE_CONNECTION_GROUP.ACTION_LOGOUT' | translate}}</a>
</div>
<!-- User menu -->
<guac-user-menu permissions="permissions"></guac-user-menu>
<!-- Main property editor -->
<h2>{{'MANAGE_CONNECTION_GROUP.SECTION_HEADER_EDIT_CONNECTION_GROUP' | translate}}</h2>

View File

@@ -22,10 +22,8 @@ THE SOFTWARE.
<div class="view" ng-class="{loading: !isLoaded()}">
<div class="logout-panel">
<a class="back button" href="#/manage/">{{'MANAGE_USER.ACTION_NAVIGATE_BACK' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'MANAGE_USER.ACTION_LOGOUT' | translate}}</a>
</div>
<!-- User menu -->
<guac-user-menu permissions="permissions"></guac-user-menu>
<!-- Main property editor -->
<h2>{{'MANAGE_USER.SECTION_HEADER_EDIT_USER' | translate}}</h2>

View File

@@ -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
};
}]);

View File

@@ -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%;
}

View File

@@ -0,0 +1,55 @@
<div class="user-menu">
<!--
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 class="change-password button" ng-click="showPasswordUpdate()" ng-show="canChangePassword">{{'USER_MENU.ACTION_CHANGE_PASSWORD' | translate}}</a>
<div class="password-dialog" ng-class="{shown: showPasswordDialog}">
<!-- Password editor -->
<div class="section">
<table class="fields">
<tr>
<th>{{'USER_MENU.FIELD_HEADER_PASSWORD_OLD' | translate}}</th>
<td><input ng-model="oldPassword" type="password" /></td>
</tr>
<tr>
<th>{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW' | translate}}</th>
<td><input ng-model="newPassword" type="password" /></td>
</tr>
<tr>
<th>{{'USER_MENU.FIELD_HEADER_PASSWORD_NEW_AGAIN' | translate}}</th>
<td><input ng-model="newPasswordMatch" type="password" /></td>
</tr>
</table>
</div>
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-click="updatePassword()">{{'USER_MENU.ACTION_SAVE' | translate}}</button>
<button ng-click="closePasswordUpdate()">{{'USER_MENU.ACTION_CANCEL' | translate}}</button>
</div>
</div>
<a class="home button" href="#/">{{'USER_MENU.ACTION_NAVIGATE_HOME' | translate}}</a>
<a class="manage button" ng-show="canManageGuacamole" href="#/manage">{{'USER_MENU.ACTION_MANAGE' | translate}}</a>
<a class="logout button" ng-click="logout()">{{'USER_MENU.ACTION_LOGOUT' | translate}}</a>
</div>

View File

@@ -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', []);

View File

@@ -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."
}
}