GUACAMOLE-220: Migrate user management controller to ManagementPermissions.

This commit is contained in:
Michael Jumper
2018-05-01 00:03:52 -07:00
parent 507202d1f3
commit 4f43ddc420
2 changed files with 184 additions and 360 deletions

View File

@@ -24,6 +24,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
function manageUserController($scope, $injector) {
// Required types
var ManagementPermissions = $injector.get('ManagementPermissions');
var PageDefinition = $injector.get('PageDefinition');
var PermissionFlagSet = $injector.get('PermissionFlagSet');
var PermissionSet = $injector.get('PermissionSet');
@@ -32,6 +33,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
// Required services
var $location = $injector.get('$location');
var $routeParams = $injector.get('$routeParams');
var $q = $injector.get('$q');
var authenticationService = $injector.get('authenticationService');
var dataSourceService = $injector.get('dataSourceService');
var guacNotification = $injector.get('guacNotification');
@@ -128,13 +130,31 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
$scope.permissionFlags = null;
/**
* 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.
* The set of permissions that will be added to the user when the user is
* saved. Permissions will only be present in this set if they are
* manually added, and not later manually removed before saving.
*
* @type Object.<String, PermissionSet>
* @type PermissionSet
*/
$scope.permissions = null;
$scope.permissionsAdded = new PermissionSet();
/**
* The set of permissions that will be removed from the user when the user
* is saved. Permissions will only be present in this set if they are
* manually removed, and not later manually added before saving.
*
* @type PermissionSet
*/
$scope.permissionsRemoved = new PermissionSet();
/**
* The managment-related actions that the current user may perform on the
* user currently being created/modified, or null if the current user's
* permissions have not yet been loaded.
*
* @type ManagementPermissions
*/
$scope.managementPermissions = null;
/**
* All available user attributes. This is only the set of attribute
@@ -164,8 +184,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
return $scope.users !== null
&& $scope.permissionFlags !== null
&& $scope.rootGroups !== null
&& $scope.permissions !== null
&& $scope.managementPermissions !== null
&& $scope.attributes !== null;
};
@@ -195,97 +214,6 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
};
/**
* Returns whether the current user can change attributes explicitly
* 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 change attributes associated with the
* user being edited, false otherwise.
*/
$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 || $scope.dataSource;
// Attributes can always be set if we are creating the user
if (!$scope.userExists(dataSource))
return true;
// The administrator can always change attributes
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[dataSource],
PermissionSet.ObjectPermissionType.UPDATE, username);
};
/**
* Returns whether the current user can change/set all user attributes for
* the user being edited, regardless of whether those attributes are
* already explicitly associated with that user.
*
* @returns {Boolean}
* true if the current user can change all attributes for the user
* being edited, regardless of whether those attributes are already
* explicitly associated with that user, false otherwise.
*/
$scope.canChangeAllAttributes = function canChangeAllAttributes() {
// All attributes can be set if we are creating the user
return !$scope.userExists($scope.dataSource);
};
/**
* Returns whether the current user can change permissions of any kind
* 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(dataSource) {
// Do not check if permissions are not yet loaded
if (!$scope.permissions)
return false;
// Use currently-selected data source if unspecified
dataSource = dataSource || $scope.dataSource;
// Permissions can always be set if we are creating the user
if (!$scope.userExists(dataSource))
return true;
// The administrator can always modify 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[dataSource],
PermissionSet.ObjectPermissionType.ADMINISTER, username);
};
/**
* Returns whether the current user can edit the username of the user being
* edited within the given data source.
@@ -303,189 +231,29 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
};
/**
* 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.
* Loads the data associated with the user having the given username,
* preparing the interface for making modifications to that existing user.
*
* @param {String} [dataSource]
* The identifier of the data source to check. If omitted, this will
* default to the currently-selected data source.
* @param {String} dataSource
* The unique identifier of the data source containing the user to
* load.
*
* @returns {Boolean}
* true if the current user can save changes to the user being edited,
* false otherwise.
* @param {String} username
* The username of the user to load.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared for
* editing the given user.
*/
$scope.canSaveUser = function canSaveUser(dataSource) {
var loadExistingUser = function loadExistingUser(dataSource, username) {
return $q.all({
users : dataSourceService.apply(userService.getUser, dataSources, username),
permissions : permissionService.getPermissions(dataSource, username)
})
.then(function userDataRetrieved(values) {
// Do not check if permissions are not yet loaded
if (!$scope.permissions)
return false;
// Use currently-selected data source if unspecified
dataSource = dataSource || $scope.dataSource;
// The administrator can always save users
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 (!$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[dataSource],
PermissionSet.ObjectPermissionType.UPDATE, username);
};
/**
* 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 || $scope.dataSource;
// If we are not editing an existing user, we cannot clone
if (!$scope.userExists($scope.dataSource))
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.
*
* @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(dataSource) {
// Do not check if permissions are not yet loaded
if (!$scope.permissions)
return false;
// Use currently-selected data source if unspecified
dataSource = dataSource || $scope.dataSource;
// Can't delete what doesn't exist
if (!$scope.userExists(dataSource))
return false;
// The administrator can always delete users
if (PermissionSet.hasSystemPermission($scope.permissions[dataSource],
PermissionSet.SystemPermissionType.ADMINISTER))
return true;
// Otherwise, require explicit DELETE permission on the user
return PermissionSet.hasUserPermission($scope.permissions[dataSource],
PermissionSet.ObjectPermissionType.DELETE, username);
};
/**
* 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(dataSource) {
// Use currently-selected data source if unspecified
dataSource = dataSource || $scope.dataSource;
// User is read-only if they cannot be saved
return !$scope.canSaveUser(dataSource);
};
// 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 = $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;
// Only the selected data source is relevant when cloning
if (cloneSourceUsername && dataSource !== $scope.dataSource)
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 : className
}));
});
});
// Pull user attribute schema
schemaService.getUserAttributes($scope.dataSource).then(function attributesReceived(attributes) {
$scope.attributes = attributes;
}, requestService.WARN);
// Pull user data and permissions if we are editing an existing user
if (username) {
// 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[$scope.dataSource];
$scope.users = values.users;
$scope.user = values.users[dataSource];
// Create skeleton user if user does not exist
if (!$scope.user)
@@ -493,54 +261,57 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
'username' : username
});
}, requestService.WARN);
// The current user will be associated with username of the existing
// user in the retrieved permission set
$scope.selfUsername = username;
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
// Pull user permissions
permissionService.getPermissions($scope.dataSource, username).then(function gotPermissions(permissions) {
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
});
};
/**
* Loads the data associated with the user having the given username,
* preparing the interface for cloning that existing user.
*
* @param {String} dataSource
* The unique identifier of the data source containing the user to
* be cloned.
*
* @param {String} username
* The username of the user being cloned.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared for
* cloning the given user.
*/
var loadClonedUser = function loadClonedUser(dataSource, username) {
return $q.all({
users : dataSourceService.apply(userService.getUser, [dataSource], username),
permissions : permissionService.getPermissions(dataSource, username)
})
.then(function userDataRetrieved(values) {
// If permissions cannot be retrieved, use empty permissions
['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() {
$scope.permissionFlags = new PermissionFlagSet();
}));
}
// 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[$scope.dataSource];
}, requestService.WARN);
$scope.user = values.users[dataSource];
// The current user will be associated with cloneSourceUsername in the
// retrieved permission set
$scope.selfUsername = cloneSourceUsername;
$scope.selfUsername = username;
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(values.permissions);
$scope.permissionsAdded = values.permissions;
// Pull user permissions
permissionService.getPermissions($scope.dataSource, cloneSourceUsername)
.then(function gotPermissions(permissions) {
$scope.permissionFlags = PermissionFlagSet.fromPermissionSet(permissions);
$scope.permissionsAdded = permissions;
})
});
};
// If permissions cannot be retrieved, use empty permissions
['catch'](requestService.createErrorCallback(function permissionRetrievalFailed() {
$scope.permissionFlags = new PermissionFlagSet();
}));
}
// Use skeleton data if we are creating a new user
else {
/**
* Loads skeleton user data, preparing the interface for creating a new
* user.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared for
* creating a new user.
*/
var loadSkeletonUser = function loadSkeletonUser() {
// No users exist regardless of data source if there is no username
$scope.users = {};
@@ -554,36 +325,89 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto
// permissions
$scope.selfUsername = 'SELF';
}
return $q.resolve();
};
/**
* Loads the data requred for performing the management task requested
* through the route parameters given at load time, automatically preparing
* the interface for editing an existing user, cloning an existing user, or
* creating an entirely new user.
*
* @returns {Promise}
* A promise which is resolved when the interface has been prepared
* for performing the requested management task.
*/
var loadRequestedUser = function loadRequestedUser() {
// Pull user data and permissions if we are editing an existing user
if (username)
return loadExistingUser($scope.dataSource, username);
// If we are cloning an existing user, pull his/her data instead
if (cloneSourceUsername)
return loadClonedUser($scope.dataSource, cloneSourceUsername);
return loadSkeletonUser();
};
// Populate interface with requested data
$q.all({
userData : loadRequestedUser(),
permissions : dataSourceService.apply(permissionService.getEffectivePermissions, dataSources, currentUsername),
attributes : schemaService.getUserAttributes($scope.dataSource)
})
.then(function dataReceived(values) {
var managementPermissions = {};
$scope.attributes = values.attributes;
// Generate pages for each applicable data source
$scope.accountPages = [];
angular.forEach(dataSources, function addAccountPage(dataSource) {
// Determine whether data source contains this user
var exists = (dataSource in $scope.users);
// Calculate management actions available for this specific account
managementPermissions[dataSource] = ManagementPermissions.fromPermissionSet(
values.permissions[dataSource],
PermissionSet.SystemPermissionType.CREATE_USER,
PermissionSet.hasUserPermission,
exists ? username : null);
// Account is not relevant if it does not exist and cannot be
// created
var readOnly = !managementPermissions[dataSource].canSaveObject;
if (!exists && readOnly)
return;
// Only the selected data source is relevant when cloning
if (cloneSourceUsername && dataSource !== $scope.dataSource)
return;
// Determine class name based on read-only / linked status
var className;
if (readOnly) className = 'read-only';
else if (exists) 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 : className
}));
});
$scope.managementPermissions = managementPermissions[$scope.dataSource];
// Query the user's permissions for the current user
dataSourceService.apply(
permissionService.getEffectivePermissions,
dataSources,
currentUsername
)
.then(function permissionsReceived(permissions) {
$scope.permissions = permissions;
}, requestService.WARN);
/**
* The set of permissions that will be added to the user when the user is
* saved. Permissions will only be present in this set if they are
* manually added, and not later manually removed before saving.
*
* @type PermissionSet
*/
$scope.permissionsAdded = new PermissionSet();
/**
* The set of permissions that will be removed from the user when the user
* is saved. Permissions will only be present in this set if they are
* manually removed, and not later manually added before saving.
*
* @type PermissionSet
*/
$scope.permissionsRemoved = new PermissionSet();
/**
* Cancels all pending edits, returning to the management page.
*/

View File

@@ -11,12 +11,12 @@
</div>
<!-- Warn if user is read-only -->
<div class="section" ng-show="isReadOnly()">
<div class="section" ng-hide="managementPermissions.canSaveObject">
<p class="notice read-only">{{'MANAGE_USER.INFO_READ_ONLY' | translate}}</p>
</div>
<!-- Sections applicable to non-read-only users -->
<div ng-show="!isReadOnly()">
<div ng-show="managementPermissions.canSaveObject">
<!-- User password section -->
<div class="section">
@@ -40,13 +40,13 @@
</div>
<!-- User attributes section -->
<div class="attributes" ng-show="canChangeAttributes()">
<div class="attributes" ng-show="managementPermissions.canChangeAttributes">
<guac-form namespace="'USER_ATTRIBUTES'" content="attributes"
model="user.attributes" model-only="!canChangeAllAttributes()"></guac-form>
model="user.attributes" model-only="!managementPermissions.canChangeAllAttributes"></guac-form>
</div>
<!-- System permissions section -->
<system-permission-editor ng-show="canChangePermissions()"
<system-permission-editor ng-show="managementPermissions.canChangePermissions"
username="selfUsername"
data-data-source="dataSource"
permission-flags="permissionFlags"
@@ -55,7 +55,7 @@
</system-permission-editor>
<!-- Connection permissions section -->
<connection-permission-editor ng-show="canChangePermissions()"
<connection-permission-editor ng-show="managementPermissions.canChangePermissions"
data-data-source="dataSource"
permission-flags="permissionFlags"
permissions-added="permissionsAdded"
@@ -64,10 +64,10 @@
<!-- Form action buttons -->
<div class="action-buttons">
<button ng-show="canSaveUser()" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
<button ng-show="canCloneUser()" ng-click="cloneUser()">{{'MANAGE_USER.ACTION_CLONE' | translate}}</button>
<button ng-show="managementPermissions.canSaveObject" ng-click="saveUser()">{{'MANAGE_USER.ACTION_SAVE' | translate}}</button>
<button ng-show="managementPermissions.canCloneObject" ng-click="cloneUser()">{{'MANAGE_USER.ACTION_CLONE' | translate}}</button>
<button ng-click="cancel()">{{'MANAGE_USER.ACTION_CANCEL' | translate}}</button>
<button ng-show="canDeleteUser()" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
<button ng-show="managementPermissions.canDeleteObject" ng-click="deleteUser()" class="danger">{{'MANAGE_USER.ACTION_DELETE' | translate}}</button>
</div>
</div>