mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUACAMOLE-926: Create users and groups; don't require them to exist beforehand.
This commit is contained in:
@@ -24,19 +24,73 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
||||
function importConnectionsController($scope, $injector) {
|
||||
|
||||
// Required services
|
||||
const $routeParams = $injector.get('$routeParams');
|
||||
const connectionParseService = $injector.get('connectionParseService');
|
||||
const connectionService = $injector.get('connectionService');
|
||||
|
||||
// Required types
|
||||
const DirectoryPatch = $injector.get('DirectoryPatch');
|
||||
const ParseError = $injector.get('ParseError');
|
||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||
|
||||
function handleSuccess(data) {
|
||||
console.log("OMG SUCCESS: ", data)
|
||||
/**
|
||||
* Given a successful response to an import PATCH request, make another
|
||||
* request to delete every created connection in the provided request, i.e.
|
||||
* clean up every connection that was created.
|
||||
*
|
||||
* @param {DirectoryPatchResponse} creationResponse
|
||||
*/
|
||||
function cleanUpConnections(creationResponse) {
|
||||
|
||||
// The patches to delete - one delete per initial creation
|
||||
const deletionPatches = creationResponse.patches.map(patch =>
|
||||
new DirectoryPatch({
|
||||
op: 'remove',
|
||||
path: '/' + patch.identifier
|
||||
}));
|
||||
|
||||
console.log("Deletion Patches", deletionPatches);
|
||||
|
||||
connectionService.patchConnections(
|
||||
$routeParams.dataSource, deletionPatches)
|
||||
|
||||
.then(deletionResponse =>
|
||||
console.log("Deletion response", deletionResponse))
|
||||
.catch(handleParseError);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a successfully parsed import file, creating any specified
|
||||
* connections, creating and granting permissions to any specified users
|
||||
* and user groups.
|
||||
*
|
||||
* TODO:
|
||||
* - Do batch import of connections
|
||||
* - Create all users/groups not already present
|
||||
* - Grant permissions to all users/groups as defined in the import file
|
||||
* - On failure: Roll back everything (maybe ask the user first):
|
||||
* - Attempt to delete all created connections
|
||||
* - Attempt to delete any created users / groups
|
||||
*
|
||||
* @param {ParseResult} parseResult
|
||||
* The result of parsing the user-supplied import file.
|
||||
*
|
||||
*/
|
||||
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
|
||||
cleanUpConnections(response);
|
||||
});
|
||||
}
|
||||
|
||||
// Set any caught error message to the scope for display
|
||||
const handleError = error => {
|
||||
const handleParseError = error => {
|
||||
console.error(error);
|
||||
$scope.error = error;
|
||||
}
|
||||
@@ -44,14 +98,25 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
||||
// Clear the current error
|
||||
const clearError = () => delete $scope.error;
|
||||
|
||||
function processData(type, data) {
|
||||
/**
|
||||
* Process the uploaded import file, importing the connections, granting
|
||||
* connection permissions, or displaying errors to the user if there are
|
||||
* problems with the provided file.
|
||||
*
|
||||
* @param {String} mimeType
|
||||
* The MIME type of the uploaded data file.
|
||||
*
|
||||
* @param {String} data
|
||||
* The raw string contents of the import file.
|
||||
*/
|
||||
function processData(mimeType, data) {
|
||||
|
||||
// The function that will process all the raw data and return a list of
|
||||
// patches to be submitted to the API
|
||||
let processDataCallback;
|
||||
|
||||
// Parse the data based on the provided mimetype
|
||||
switch(type) {
|
||||
switch(mimeType) {
|
||||
|
||||
case "application/json":
|
||||
case "text/json":
|
||||
@@ -82,10 +147,10 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
|
||||
processDataCallback(data)
|
||||
|
||||
// Send the data off to be imported if parsing is successful
|
||||
.then(handleSuccess)
|
||||
.then(handleParseSuccess)
|
||||
|
||||
// Display any error found while parsing the file
|
||||
.catch(handleError);
|
||||
.catch(handleParseError);
|
||||
}
|
||||
|
||||
$scope.upload = function() {
|
||||
|
@@ -33,7 +33,7 @@ angular.module('import').factory('connectionCSVService',
|
||||
|
||||
// Required types
|
||||
const ParseError = $injector.get('ParseError');
|
||||
const ProtoConnection = $injector.get('ProtoConnection');
|
||||
const ImportConnection = $injector.get('ImportConnection');
|
||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||
|
||||
// Required services
|
||||
@@ -154,7 +154,7 @@ angular.module('import').factory('connectionCSVService',
|
||||
|
||||
/**
|
||||
* Given a CSV header row, create and return a promise that will resolve to
|
||||
* a function that can take a CSV data row and return a ProtoConnection
|
||||
* a function that can take a CSV data row and return a ImportConnection
|
||||
* object. If an error occurs while parsing a particular row, the resolved
|
||||
* function will throw a ParseError describing the failure.
|
||||
*
|
||||
@@ -185,7 +185,7 @@ angular.module('import').factory('connectionCSVService',
|
||||
*
|
||||
* @returns {Promise.<Function.<String[], Object>>}
|
||||
* A promise that will resolve to a function that translates a CSV data
|
||||
* row (array of strings) to a ProtoConnection object.
|
||||
* row (array of strings) to a ImportConnection object.
|
||||
*/
|
||||
service.getCSVTransformer = function getCSVTransformer(headerRow) {
|
||||
|
||||
@@ -405,7 +405,7 @@ angular.module('import').factory('connectionCSVService',
|
||||
const parentIdentifier = (
|
||||
parentIdentifierGetter && parentIdentifierGetter(row));
|
||||
|
||||
return new ProtoConnection({
|
||||
return new ImportConnection({
|
||||
|
||||
// Fields that are not protocol-specific
|
||||
...{
|
||||
|
@@ -42,82 +42,9 @@ angular.module('import').factory('connectionParseService',
|
||||
const schemaService = $injector.get('schemaService');
|
||||
const connectionCSVService = $injector.get('connectionCSVService');
|
||||
const connectionGroupService = $injector.get('connectionGroupService');
|
||||
const userService = $injector.get('userService');
|
||||
const userGroupService = $injector.get('userGroupService');
|
||||
|
||||
const service = {};
|
||||
|
||||
/**
|
||||
* Resolves to an object whose keys are all valid identifiers in the current
|
||||
* data source. The provided `requestFunction` should resolve to such an
|
||||
* object when provided with the current data source.
|
||||
*
|
||||
* @param {Function.<String<Object.<String, *>>} requestFunction
|
||||
* A function that, given a data source, will return a promise resolving
|
||||
* to an object with keys that are unique identifiers for entities in
|
||||
* that data source.
|
||||
*
|
||||
* @returns {Promise.<Function[String[], String[]>>}
|
||||
* A promise that will resolve to an function that, given an array of
|
||||
* identifiers, will return all identifiers that do not exist as keys in
|
||||
* the object returned by `requestFunction`, i.e. all identifiers that
|
||||
* do not exist in the current data source.
|
||||
*/
|
||||
function getIdentifierMap(requestFunction) {
|
||||
|
||||
// The current data source to which all the identifiers will belong
|
||||
const dataSource = $routeParams.dataSource;
|
||||
|
||||
// Make the request and return the response, which should be an object
|
||||
// whose keys are all valid identifiers for the current data source
|
||||
return requestFunction(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a promise that resolves to a function that takes an array of
|
||||
* identifiers, returning a ParseError describing the invalid identifiers
|
||||
* from the list. The provided `requestFunction` should resolve to such an
|
||||
* object whose keys are all valid identifiers when provided with the
|
||||
* current data source.
|
||||
*
|
||||
* @param {Function.<String<Object.<String, *>>} requestFunction
|
||||
* A function that, given a data source, will return a promise resolving
|
||||
* to an object with keys that are unique identifiers for entities in
|
||||
* that data source.
|
||||
*
|
||||
* @returns {Promise.<Function.<String[], ParseError?>>}
|
||||
* A promise that will resolve to a function to check the validity of
|
||||
* each identifier in a provided array, returning a ParseError
|
||||
* describing the problem if any are not valid.
|
||||
*/
|
||||
function getInvalidIdentifierErrorChecker(requestFunction) {
|
||||
|
||||
// Fetch all the valid user identifiers in the system, and
|
||||
return getIdentifierMap(requestFunction).then(validIdentifiers =>
|
||||
|
||||
// The resolved function that takes a list of user group identifiers
|
||||
allIdentifiers => {
|
||||
|
||||
// Filter to only include invalid identifiers
|
||||
const invalidIdentifiers = _.filter(allIdentifiers,
|
||||
identifier => !validIdentifiers[identifier]);
|
||||
|
||||
if (invalidIdentifiers.length) {
|
||||
|
||||
// Quote and comma-seperate for display
|
||||
const identifierList = invalidIdentifiers.map(
|
||||
identifier => '"' + identifier + '"').join(', ');
|
||||
|
||||
return new ParseError({
|
||||
message: 'Invalid User Group Identifiers: ' + identifierList,
|
||||
key: 'CONNECTION_IMPORT.ERROR_INVALID_USER_GROUP_IDENTIFIERS',
|
||||
variables: { IDENTIFIER_LIST: identifierList }
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform basic checks, common to all file types - namely that the parsed
|
||||
* data is an array, and contains at least one connection entry. Returns an
|
||||
@@ -245,12 +172,12 @@ angular.module('import').factory('connectionParseService',
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a provided ProtoConnection array into a ParseResult. Any provided
|
||||
* Convert a provided ImportConnection array into a ParseResult. Any provided
|
||||
* transform functions will be run on each entry in `connectionData` before
|
||||
* any other processing is done.
|
||||
*
|
||||
* @param {*[]} connectionData
|
||||
* An arbitrary array of data. This must evaluate to a ProtoConnection
|
||||
* An arbitrary array of data. This must evaluate to a ImportConnection
|
||||
* object after being run through all functions in `transformFunctions`.
|
||||
*
|
||||
* @param {Function[]} transformFunctions
|
||||
@@ -271,21 +198,10 @@ angular.module('import').factory('connectionParseService',
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
return $q.all({
|
||||
groupTransformer : getGroupTransformer(),
|
||||
invalidUserIdErrorDetector : getInvalidIdentifierErrorChecker(
|
||||
userService.getUsers),
|
||||
invalidGroupIDErrorDetector : getInvalidIdentifierErrorChecker(
|
||||
userGroupService.getUserGroups),
|
||||
})
|
||||
|
||||
// Transform the rows from the CSV file to an array of API patches
|
||||
// and lists of user and group identifiers
|
||||
.then(({groupTransformer,
|
||||
invalidUserIdErrorDetector, invalidGroupIDErrorDetector}) =>
|
||||
return getGroupTransformer().then(groupTransformer =>
|
||||
connectionData.reduce((parseResult, data) => {
|
||||
|
||||
const { patches, identifiers, users, groups } = parseResult;
|
||||
const { patches, users, groups, allUsers, allGroups } = parseResult;
|
||||
|
||||
// Run the array data through each provided transform
|
||||
let connectionObject = data;
|
||||
@@ -307,14 +223,15 @@ angular.module('import').factory('connectionParseService',
|
||||
connectionErrors.push(error);
|
||||
}
|
||||
|
||||
// Push any errors for invalid user or user group identifiers
|
||||
const pushError = error => error && connectionErrors.push(error);
|
||||
pushError(invalidUserIdErrorDetector(connectionObject.users));
|
||||
pushError(invalidGroupIDErrorDetector(connectionObject.userGroups));
|
||||
|
||||
// Add the user and group identifiers for this connection
|
||||
users.push(connectionObject.users);
|
||||
groups.push(connectionObject.groups);
|
||||
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);
|
||||
|
||||
// Translate to a full-fledged Connection
|
||||
const connection = new Connection(connectionObject);
|
||||
@@ -398,7 +315,18 @@ angular.module('import').factory('connectionParseService',
|
||||
service.parseYAML = function parseYAML(yamlData) {
|
||||
|
||||
// Parse from YAML into a javascript array
|
||||
try {
|
||||
const connectionData = parseYAMLData(yamlData);
|
||||
}
|
||||
|
||||
// If the YAML parser throws an error, reject with that error. No
|
||||
// translation key will be available here.
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
const deferred = $q.defer();
|
||||
deferred.reject(new ParseError({ message: error.message }));
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Produce a ParseResult
|
||||
return parseConnectionData(connectionData);
|
||||
@@ -420,7 +348,18 @@ angular.module('import').factory('connectionParseService',
|
||||
service.parseJSON = function parseJSON(jsonData) {
|
||||
|
||||
// Parse from JSON into a javascript array
|
||||
try {
|
||||
const connectionData = JSON.parse(jsonData);
|
||||
}
|
||||
|
||||
// If the JSON parse attempt throws an error, reject with that error.
|
||||
// No translation key will be available here.
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
const deferred = $q.defer();
|
||||
deferred.reject(new ParseError({ message: error.message }));
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Produce a ParseResult
|
||||
return parseConnectionData(connectionData);
|
||||
|
@@ -18,21 +18,21 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service which defines the Connection class.
|
||||
* Service which defines the ImportConnection class.
|
||||
*/
|
||||
angular.module('import').factory('ProtoConnection', [
|
||||
function defineProtoConnection() {
|
||||
angular.module('import').factory('ImportConnection', [
|
||||
function defineImportConnection() {
|
||||
|
||||
/**
|
||||
* A representation of a connection to be imported, as parsed from an
|
||||
* user-supplied import file.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Connection|Object} [template={}]
|
||||
* @param {ImportConnection|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* Connection.
|
||||
*/
|
||||
var ProtoConnection = function ProtoConnection(template) {
|
||||
var ImportConnection = function ImportConnection(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
@@ -106,6 +106,6 @@ angular.module('import').factory('ProtoConnection', [
|
||||
|
||||
};
|
||||
|
||||
return ProtoConnection;
|
||||
return ImportConnection;
|
||||
|
||||
}]);
|
@@ -63,6 +63,23 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
|
||||
*/
|
||||
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
|
||||
* group specified in the batch import. i.e. a set of all user group
|
||||
* identifiers.
|
||||
*
|
||||
* @type {Object.<String, Boolean>}
|
||||
*/
|
||||
this.allGroups = template.allGroups || {};
|
||||
|
||||
/**
|
||||
* An array of errors encountered while parsing the corresponding
|
||||
* connection (at the same array index). Each connection should have a
|
||||
|
@@ -181,13 +181,15 @@ angular.module('rest').factory('connectionService', ['$injector',
|
||||
})
|
||||
|
||||
// Clear the cache
|
||||
.then(function connectionsPatched(){
|
||||
.then(function connectionsPatched(patchResponse){
|
||||
cacheService.connections.removeAll();
|
||||
|
||||
// Clear users cache to force reload of permissions for any
|
||||
// newly created or replaced connections
|
||||
cacheService.users.removeAll();
|
||||
|
||||
return patchResponse;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
@@ -59,10 +59,9 @@ angular.module('rest').factory('DirectoryPatch', [function defineDirectoryPatch(
|
||||
this.path = template.path || '/';
|
||||
|
||||
/**
|
||||
* The object being added or replaced, or the identifier of the object
|
||||
* being removed.
|
||||
* The object being added, or undefined if deleting.
|
||||
*
|
||||
* @type {DirectoryObject|String}
|
||||
* @type {DirectoryObject}
|
||||
*/
|
||||
this.value = template.value;
|
||||
|
||||
@@ -78,11 +77,6 @@ angular.module('rest').factory('DirectoryPatch', [function defineDirectoryPatch(
|
||||
*/
|
||||
ADD : "add",
|
||||
|
||||
/**
|
||||
* Removes the specified object from the relation.
|
||||
*/
|
||||
REPLACE : "replace",
|
||||
|
||||
/**
|
||||
* Removes the specified object from the relation.
|
||||
*/
|
||||
|
@@ -30,15 +30,11 @@ angular.module('rest').factory('DirectoryPatchOutcome', [
|
||||
* response. The error field is only meaningful for unsuccessful patches.
|
||||
* @constructor
|
||||
*
|
||||
* @template DirectoryObject
|
||||
* The directory-based object type that this DirectoryPatchOutcome
|
||||
* represents a patch outcome for.
|
||||
*
|
||||
* @param {DirectoryObject|Object} [template={}]
|
||||
* @param {DirectoryPatchOutcome|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* DirectoryPatchOutcome.
|
||||
*/
|
||||
var DirectoryPatchOutcome = function DirectoryPatchOutcome(template) {
|
||||
const DirectoryPatchOutcome = function DirectoryPatchOutcome(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
@@ -78,6 +74,6 @@ angular.module('rest').factory('DirectoryPatchOutcome', [
|
||||
|
||||
};
|
||||
|
||||
return DirectoryPatch;
|
||||
return DirectoryPatchOutcome;
|
||||
|
||||
}]);
|
||||
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Service which defines the DirectoryPatchResponse class.
|
||||
*/
|
||||
angular.module('rest').factory('DirectoryPatchResponse', [
|
||||
function defineDirectoryPatchResponse() {
|
||||
|
||||
/**
|
||||
* An object returned by a PATCH request to a directory REST API,
|
||||
* representing the successful response to a patch request.
|
||||
*
|
||||
* @param {DirectoryPatchResponse|Object} [template={}]
|
||||
* The object whose properties should be copied within the new
|
||||
* DirectoryPatchResponse.
|
||||
*/
|
||||
const DirectoryPatchResponse = function DirectoryPatchResponse(template) {
|
||||
|
||||
// Use empty object by default
|
||||
template = template || {};
|
||||
|
||||
/**
|
||||
* An outcome for each patch in the corresponding patch request.
|
||||
*
|
||||
* @type {DirectoryPatchOutcome[]}
|
||||
*/
|
||||
this.patches = template.patches;
|
||||
|
||||
};
|
||||
|
||||
return DirectoryPatchResponse;
|
||||
|
||||
}]);
|
Reference in New Issue
Block a user