GUACAMOLE-926: Grant permissions to all users / groups.

This commit is contained in:
James Muehlner
2023-02-14 02:12:20 +00:00
parent bfb7f3b78a
commit 51e0fb8c66
5 changed files with 253 additions and 39 deletions

View File

@@ -17,6 +17,8 @@
* under the License.
*/
/* global _ */
/**
* The controller for the connection import page.
*/
@@ -24,14 +26,21 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
function importConnectionsController($scope, $injector) {
// Required services
const $q = $injector.get('$q');
const $routeParams = $injector.get('$routeParams');
const connectionParseService = $injector.get('connectionParseService');
const connectionService = $injector.get('connectionService');
const permissionService = $injector.get('permissionService');
const userService = $injector.get('userService');
const userGroupService = $injector.get('userGroupService');
// Required types
const DirectoryPatch = $injector.get('DirectoryPatch');
const ParseError = $injector.get('ParseError');
const PermissionSet = $injector.get('PermissionSet');
const TranslatableMessage = $injector.get('TranslatableMessage');
const User = $injector.get('User');
const UserGroup = $injector.get('UserGroup');
/**
* Given a successful response to an import PATCH request, make another
@@ -56,10 +65,133 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
.then(deletionResponse =>
console.log("Deletion response", deletionResponse))
.catch(handleParseError);
.catch(handleError);
}
/**
* Create all users and user groups mentioned in the import file that don't
* already exist in the current data source.
*
* @param {ParseResult} parseResult
* The result of parsing the user-supplied import file.
*
* @return {Object}
* An object containing the results of the calls to create the users
* and groups.
*/
function createUsersAndGroups(parseResult) {
const dataSource = $routeParams.dataSource;
return $q.all({
existingUsers : userService.getUsers(dataSource),
existingGroups : userGroupService.getUserGroups(dataSource)
}).then(({existingUsers, existingGroups}) => {
const userPatches = Object.keys(parseResult.users)
// Filter out any existing users
.filter(identifier => !existingUsers[identifier])
// A patch to create each new user
.map(username => new DirectoryPatch({
op: 'add',
path: '/',
value: new User({ username })
}));
const groupPatches = Object.keys(parseResult.groups)
// Filter out any existing groups
.filter(identifier => !existingGroups[identifier])
// A patch to create each new user group
.map(identifier => new DirectoryPatch({
op: 'add',
path: '/',
value: new UserGroup({ identifier })
}));
return $q.all({
createdUsers: userService.patchUsers(dataSource, userPatches),
createdGroups: userGroupService.patchUserGroups(dataSource, groupPatches)
});
});
}
/**
* Grant read permissions for each user and group in the supplied parse
* result to each connection in their connection list. Note that there will
* be a seperate request for each user and group.
*
* @param {ParseResult} parseResult
* The result of successfully parsing a user-supplied import file.
*
* @param {Object} response
* The response from the PATCH API request.
*
* @returns {Promise.<Object>}
* A promise that will resolve with the result of every permission
* granting request.
*/
function grantConnectionPermissions(parseResult, response) {
const dataSource = $routeParams.dataSource;
// All connection grant requests, one per user/group
const userRequests = {};
const groupRequests = {};
// Create a PermissionSet granting access to all connections at
// the provided indices within the provided parse result
const createPermissionSet = indices =>
new PermissionSet({ connectionPermissions: indices.reduce(
(permissions, index) => {
const connectionId = response.patches[index].identifier;
permissions[connectionId] = [
PermissionSet.ObjectPermissionType.READ];
return permissions;
}, {}) });
// Now that we've created all the users, grant access to each
_.forEach(parseResult.users, (connectionIndices, identifier) =>
// Grant the permissions - note the group flag is `false`
userRequests[identifier] = permissionService.patchPermissions(
dataSource, identifier,
// Create the permissions to these connections for this user
createPermissionSet(connectionIndices),
// Do not remove any permissions
new PermissionSet(),
// This call is not for a group
false));
// Now that we've created all the groups, grant access to each
_.forEach(parseResult.groups, (connectionIndices, identifier) =>
// Grant the permissions - note the group flag is `true`
groupRequests[identifier] = permissionService.patchPermissions(
dataSource, identifier,
// Create the permissions to these connections for this user
createPermissionSet(connectionIndices),
// Do not remove any permissions
new PermissionSet(),
// This call is for a group
true));
// Return the result from all the permission granting calls
return $q.all({ ...userRequests, ...groupRequests });
}
/**
* Process a successfully parsed import file, creating any specified
* connections, creating and granting permissions to any specified users
@@ -78,19 +210,32 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
*
*/
function handleParseSuccess(parseResult) {
connectionService.patchConnections(
$routeParams.dataSource, parseResult.patches)
const dataSource = $routeParams.dataSource;
console.log("parseResult", parseResult);
// First, attempt to create the connections
connectionService.patchConnections(dataSource, parseResult.patches)
.then(response => {
console.log("Creation Response", response);
// If connection creation is successful, create users and groups
createUsersAndGroups(parseResult).then(() => {
grantConnectionPermissions(parseResult, response).then(results => {
console.log("permission requests", results);
// TODON'T: Delete connections so we can test over and over
cleanUpConnections(response);
})
});
});
}
// Set any caught error message to the scope for display
const handleParseError = error => {
const handleError = error => {
console.error(error);
$scope.error = error;
}
@@ -150,7 +295,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
.then(handleParseSuccess)
// Display any error found while parsing the file
.catch(handleParseError);
.catch(handleError);
}
$scope.upload = function() {

View File

@@ -199,9 +199,9 @@ angular.module('import').factory('connectionParseService',
}
return getGroupTransformer().then(groupTransformer =>
connectionData.reduce((parseResult, data) => {
connectionData.reduce((parseResult, data, index) => {
const { patches, users, groups, allUsers, allGroups } = parseResult;
const { patches, users, groups } = parseResult;
// Run the array data through each provided transform
let connectionObject = data;
@@ -223,15 +223,33 @@ angular.module('import').factory('connectionParseService',
connectionErrors.push(error);
}
// Add the user and group identifiers for this connection
// The users and user groups that should be granted access
const connectionUsers = connectionObject.users || [];
const connectionGroups = connectionObject.groups || [];
users.push(connectionUsers);
groups.push(connectionGroups);
// Add all user and user group identifiers to the overall sets
connectionUsers.forEach(identifier => allUsers[identifier] = true);
connectionGroups.forEach(identifier => allGroups[identifier] = true);
// Add this connection index to the list for each user
connectionUsers.forEach(identifier => {
// If there's an existing list, add the index to that
if (users[identifier])
users[identifier].push(index);
// Otherwise, create a new list with just this index
else
users[identifier] = [index];
});
// Add this connection index to the list for each group
connectionGroups.forEach(identifier => {
// If there's an existing list, add the index to that
if (groups[identifier])
groups[identifier].push(index);
// Otherwise, create a new list with just this index
else
groups[identifier] = [index];
});
// Translate to a full-fledged Connection
const connection = new Connection(connectionObject);

View File

@@ -48,28 +48,13 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
this.patches = template.patches || [];
/**
* A list of user identifiers that should be granted read access to the
* the corresponding connection (at the same array index).
* An object whose keys are the user identifiers of users specified
* in the batch import. and the keys are an array of indices of
* connections to which those users should be granted access.
*
* @type {String[]}
* @type {Object.<String, Integer[]>}
*/
this.users = template.users || [];
/**
* A list of user group identifiers that should be granted read access
* to the corresponding connection (at the same array index).
*
* @type {String[]}
*/
this.groups = template.groups || [];
/**
* An object whose keys are the user identifiers of every user specified
* in the batch import. i.e. a set of all user identifiers.
*
* @type {Object.<String, Boolean>}
*/
this.allUsers = template.allUsers || {};
this.users = template.users || {};
/**
* An object whose keys are the user group identifiers of every user
@@ -78,7 +63,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
*
* @type {Object.<String, Boolean>}
*/
this.allGroups = template.allGroups || {};
this.groups = template.users || {};
/**
* An array of errors encountered while parsing the corresponding

View File

@@ -190,6 +190,39 @@ angular.module('rest').factory('userGroupService', ['$injector',
};
/**
* Makes a request to the REST API to apply a supplied list of user group
* patches, returning a promise that can be used for processing the results
* of the call.
*
* This operation is atomic - if any errors are encountered during the
* connection patching process, the entire request will fail, and no
* changes will be persisted.
*
* @param {DirectoryPatch.<UserGroup>[]} patches
* An array of patches to apply.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* patch operation is successful.
*/
service.patchUserGroups = function patchUserGroups(dataSource, patches) {
// Make the PATCH request
return authenticationService.request({
method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/userGroups',
data : patches
})
// Clear the cache
.then(function userGroupsPatched(patchResponse){
cacheService.users.removeAll();
return patchResponse;
});
}
return service;
}]);

View File

@@ -236,6 +236,39 @@ angular.module('rest').factory('userService', ['$injector',
};
/**
* Makes a request to the REST API to apply a supplied list of user patches,
* returning a promise that can be used for processing the results of the
* call.
*
* This operation is atomic - if any errors are encountered during the
* connection patching process, the entire request will fail, and no
* changes will be persisted.
*
* @param {DirectoryPatch.<User>[]} patches
* An array of patches to apply.
*
* @returns {Promise}
* A promise for the HTTP call which will succeed if and only if the
* patch operation is successful.
*/
service.patchUsers = function patchUsers(dataSource, patches) {
// Make the PATCH request
return authenticationService.request({
method : 'PATCH',
url : 'api/session/data/' + encodeURIComponent(dataSource) + '/users',
data : patches
})
// Clear the cache
.then(function usersPatched(patchResponse){
cacheService.users.removeAll();
return patchResponse;
});
}
return service;
}]);