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. * under the License.
*/ */
/* global _ */
/** /**
* The controller for the connection import page. * The controller for the connection import page.
*/ */
@@ -24,14 +26,21 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
function importConnectionsController($scope, $injector) { function importConnectionsController($scope, $injector) {
// Required services // Required services
const $q = $injector.get('$q');
const $routeParams = $injector.get('$routeParams'); const $routeParams = $injector.get('$routeParams');
const connectionParseService = $injector.get('connectionParseService'); const connectionParseService = $injector.get('connectionParseService');
const connectionService = $injector.get('connectionService'); const connectionService = $injector.get('connectionService');
const permissionService = $injector.get('permissionService');
const userService = $injector.get('userService');
const userGroupService = $injector.get('userGroupService');
// Required types // Required types
const DirectoryPatch = $injector.get('DirectoryPatch'); const DirectoryPatch = $injector.get('DirectoryPatch');
const ParseError = $injector.get('ParseError'); const ParseError = $injector.get('ParseError');
const PermissionSet = $injector.get('PermissionSet');
const TranslatableMessage = $injector.get('TranslatableMessage'); 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 * Given a successful response to an import PATCH request, make another
@@ -56,10 +65,133 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
.then(deletionResponse => .then(deletionResponse =>
console.log("Deletion response", 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 * Process a successfully parsed import file, creating any specified
* connections, creating and granting permissions to any specified users * connections, creating and granting permissions to any specified users
@@ -78,19 +210,32 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
* *
*/ */
function handleParseSuccess(parseResult) { function handleParseSuccess(parseResult) {
connectionService.patchConnections(
$routeParams.dataSource, parseResult.patches)
.then(response => {
console.log("Creation Response", response);
// TODON'T: Delete connections so we can test over and over const dataSource = $routeParams.dataSource;
cleanUpConnections(response);
console.log("parseResult", parseResult);
// First, attempt to create the connections
connectionService.patchConnections(dataSource, parseResult.patches)
.then(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 // Set any caught error message to the scope for display
const handleParseError = error => { const handleError = error => {
console.error(error); console.error(error);
$scope.error = error; $scope.error = error;
} }
@@ -150,7 +295,7 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
.then(handleParseSuccess) .then(handleParseSuccess)
// Display any error found while parsing the file // Display any error found while parsing the file
.catch(handleParseError); .catch(handleError);
} }
$scope.upload = function() { $scope.upload = function() {

View File

@@ -199,9 +199,9 @@ angular.module('import').factory('connectionParseService',
} }
return getGroupTransformer().then(groupTransformer => 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 // Run the array data through each provided transform
let connectionObject = data; let connectionObject = data;
@@ -223,15 +223,33 @@ angular.module('import').factory('connectionParseService',
connectionErrors.push(error); 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 connectionUsers = connectionObject.users || [];
const connectionGroups = connectionObject.groups || []; const connectionGroups = connectionObject.groups || [];
users.push(connectionUsers);
groups.push(connectionGroups);
// Add all user and user group identifiers to the overall sets // Add this connection index to the list for each user
connectionUsers.forEach(identifier => allUsers[identifier] = true); connectionUsers.forEach(identifier => {
connectionGroups.forEach(identifier => allGroups[identifier] = true);
// 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 // Translate to a full-fledged Connection
const connection = new Connection(connectionObject); const connection = new Connection(connectionObject);

View File

@@ -48,28 +48,13 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
this.patches = template.patches || []; this.patches = template.patches || [];
/** /**
* A list of user identifiers that should be granted read access to the * An object whose keys are the user identifiers of users specified
* the corresponding connection (at the same array index). * 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 || []; 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 || {};
/** /**
* An object whose keys are the user group identifiers of every user * 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>} * @type {Object.<String, Boolean>}
*/ */
this.allGroups = template.allGroups || {}; this.groups = template.users || {};
/** /**
* An array of errors encountered while parsing the corresponding * 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; return service;
}]); }]);

View File

@@ -235,6 +235,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; return service;