mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-926: Also accept user and group identifiers to post in a seperate request.
This commit is contained in:
@@ -33,6 +33,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
|
|
||||||
// Required types
|
// Required types
|
||||||
const ParseError = $injector.get('ParseError');
|
const ParseError = $injector.get('ParseError');
|
||||||
|
const ProtoConnection = $injector.get('ProtoConnection');
|
||||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
@@ -43,7 +44,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
const service = {};
|
const service = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves to an object detailing the connection
|
* Returns a promise that resolves to a object detailing the connection
|
||||||
* attributes for the current data source, as well as the connection
|
* attributes for the current data source, as well as the connection
|
||||||
* paremeters for every protocol, for the current data source.
|
* paremeters for every protocol, for the current data source.
|
||||||
*
|
*
|
||||||
@@ -97,11 +98,65 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a raw user-provided, semicolon-seperated list of identifiers into
|
||||||
|
* an array of identifiers. If identifiers contain semicolons, they can be
|
||||||
|
* escaped with backslashes, and backslashes can also be escaped using other
|
||||||
|
* backslashes.
|
||||||
|
*
|
||||||
|
* @param {type} rawIdentifiers
|
||||||
|
* The raw string value as fetched from the CSV.
|
||||||
|
*
|
||||||
|
* @returns {Array.<String>}
|
||||||
|
* An array of identifier values.
|
||||||
|
*/
|
||||||
|
function splitIdentifiers(rawIdentifiers) {
|
||||||
|
|
||||||
|
// Keep track of whether a backslash was seen
|
||||||
|
let escaped = false;
|
||||||
|
|
||||||
|
return _.reduce(rawIdentifiers, (identifiers, ch) => {
|
||||||
|
|
||||||
|
// The current identifier will be the last one in the final list
|
||||||
|
let identifier = identifiers[identifiers.length - 1];
|
||||||
|
|
||||||
|
// If a semicolon is seen, set the "escaped" flag and continue
|
||||||
|
// to the next character
|
||||||
|
if (!escaped && ch == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
return identifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the current identifier and start a new one if there's an
|
||||||
|
// unescaped semicolon
|
||||||
|
else if (!escaped && ch == ';') {
|
||||||
|
identifiers.push('');
|
||||||
|
return identifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all other cases, just append to the identifier
|
||||||
|
else {
|
||||||
|
identifier += ch;
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated identifier to the list
|
||||||
|
identifiers[identifiers.length - 1] = identifier;
|
||||||
|
|
||||||
|
return identifiers;
|
||||||
|
|
||||||
|
}, [''])
|
||||||
|
|
||||||
|
// Filter out any 0-length (empty) identifiers
|
||||||
|
.filter(identifier => identifier.length);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a CSV header row, create and return a promise that will resolve to
|
* 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 connection object.
|
* a function that can take a CSV data row and return a ProtoConnection
|
||||||
* If an error occurs while parsing a particular row, the resolved function
|
* object. If an error occurs while parsing a particular row, the resolved
|
||||||
* will throw a ParseError describing the failure.
|
* function will throw a ParseError describing the failure.
|
||||||
*
|
*
|
||||||
* The provided CSV must contain columns for name and protocol. Optionally,
|
* The provided CSV must contain columns for name and protocol. Optionally,
|
||||||
* the parentIdentifier of the target parent connection group, or a connection
|
* the parentIdentifier of the target parent connection group, or a connection
|
||||||
@@ -120,14 +175,17 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
* required.
|
* required.
|
||||||
*
|
*
|
||||||
* This returned object will be very similar to the Connection type, with
|
* This returned object will be very similar to the Connection type, with
|
||||||
* the exception that a human-readable "group" field may be present.
|
* the exception that a human-readable "group" field may be present, in
|
||||||
|
* addition to "user" and "userGroup" fields containing arrays of user and
|
||||||
|
* user group identifiers for whom read access should be granted to this
|
||||||
|
* connection.
|
||||||
*
|
*
|
||||||
* If a failure occurs while attempting to create the transformer function,
|
* If a failure occurs while attempting to create the transformer function,
|
||||||
* the promise will be rejected with a ParseError describing the failure.
|
* the promise will be rejected with a ParseError describing the failure.
|
||||||
*
|
*
|
||||||
* @returns {Promise.<Function.<String[], Object>>}
|
* @returns {Promise.<Function.<String[], Object>>}
|
||||||
* A promise that will resolve to a function that translates a CSV data
|
* A promise that will resolve to a function that translates a CSV data
|
||||||
* row (array of strings) to a connection object.
|
* row (array of strings) to a ProtoConnection object.
|
||||||
*/
|
*/
|
||||||
service.getCSVTransformer = function getCSVTransformer(headerRow) {
|
service.getCSVTransformer = function getCSVTransformer(headerRow) {
|
||||||
|
|
||||||
@@ -149,8 +207,12 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
protocolGetter: undefined,
|
protocolGetter: undefined,
|
||||||
|
|
||||||
// Callbacks for a parent group ID or group path
|
// Callbacks for a parent group ID or group path
|
||||||
groupGetter: _.noop,
|
groupGetter: undefined,
|
||||||
parentIdentifierGetter: _.noop,
|
parentIdentifierGetter: undefined,
|
||||||
|
|
||||||
|
// Callbacks for user and user group identifiers
|
||||||
|
usersGetter: () => [],
|
||||||
|
userGroupsGetter: () => [],
|
||||||
|
|
||||||
// Callbacks that will generate either connection attributes or
|
// Callbacks that will generate either connection attributes or
|
||||||
// parameters. These callbacks will return a {type, name, value}
|
// parameters. These callbacks will return a {type, name, value}
|
||||||
@@ -189,6 +251,11 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
// A callback that returns the field at the current index
|
// A callback that returns the field at the current index
|
||||||
const fetchFieldAtIndex = row => row[index];
|
const fetchFieldAtIndex = row => row[index];
|
||||||
|
|
||||||
|
// A callback that splits identifier lists by semicolon
|
||||||
|
// characters into a javascript list of identifiers
|
||||||
|
const identifierListCallback = row =>
|
||||||
|
splitIdentifiers(fetchFieldAtIndex(row));
|
||||||
|
|
||||||
// Set up the name callback
|
// Set up the name callback
|
||||||
if (header == 'name')
|
if (header == 'name')
|
||||||
transformConfig.nameGetter = fetchFieldAtIndex;
|
transformConfig.nameGetter = fetchFieldAtIndex;
|
||||||
@@ -203,7 +270,18 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
|
|
||||||
// Set up the group parent ID callback
|
// Set up the group parent ID callback
|
||||||
else if (header == 'parentIdentifier')
|
else if (header == 'parentIdentifier')
|
||||||
transformConfig.parentIdentifierGetter = fetchFieldAtIndex;
|
transformConfig.parentIdentifierGetter = (
|
||||||
|
identifierListCallback);
|
||||||
|
|
||||||
|
// Set the user identifiers callback
|
||||||
|
else if (header == 'users')
|
||||||
|
transformConfig.usersGetter = (
|
||||||
|
identifierListCallback);
|
||||||
|
|
||||||
|
// Set the user group identifiers callback
|
||||||
|
else if (header == 'groups')
|
||||||
|
transformConfig.userGroupsGetter = (
|
||||||
|
identifierListCallback);
|
||||||
|
|
||||||
// At this point, any other header might refer to a connection
|
// At this point, any other header might refer to a connection
|
||||||
// parameter or to an attribute
|
// parameter or to an attribute
|
||||||
@@ -283,46 +361,60 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
nameGetter, protocolGetter,
|
||||||
|
parentIdentifierGetter, groupGetter,
|
||||||
|
usersGetter, userGroupsGetter,
|
||||||
|
parameterOrAttributeGetters
|
||||||
|
} = transformConfig;
|
||||||
|
|
||||||
// Fail if the name wasn't provided
|
// Fail if the name wasn't provided
|
||||||
if (!transformConfig.nameGetter)
|
if (!nameGetter)
|
||||||
return deferred.reject(new ParseError({
|
return deferred.reject(new ParseError({
|
||||||
message: 'The connection name must be provided',
|
message: 'The connection name must be provided',
|
||||||
key: 'CONNECTION_IMPORT.ERROR_REQUIRED_NAME'
|
key: 'CONNECTION_IMPORT.ERROR_REQUIRED_NAME'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Fail if the protocol wasn't provided
|
// Fail if the protocol wasn't provided
|
||||||
if (!transformConfig.protocolGetter)
|
if (!protocolGetter)
|
||||||
return deferred.reject(new ParseError({
|
return deferred.reject(new ParseError({
|
||||||
message: 'The connection protocol must be provided',
|
message: 'The connection protocol must be provided',
|
||||||
key: 'CONNECTION_IMPORT.ERROR_REQUIRED_PROTOCOL'
|
key: 'CONNECTION_IMPORT.ERROR_REQUIRED_PROTOCOL'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// If both are specified, the parent group is ambigious
|
||||||
|
if (parentIdentifierGetter && groupGetter)
|
||||||
|
throw new ParseError({
|
||||||
|
message: 'Only one of group or parentIdentifier can be set',
|
||||||
|
key: 'CONNECTION_IMPORT.ERROR_AMBIGUOUS_PARENT_GROUP'
|
||||||
|
});
|
||||||
|
|
||||||
// The function to transform a CSV row into a connection object
|
// The function to transform a CSV row into a connection object
|
||||||
deferred.resolve(function transformCSVRow(row) {
|
deferred.resolve(function transformCSVRow(row) {
|
||||||
|
|
||||||
const {
|
// Get name and protocol
|
||||||
nameGetter, protocolGetter,
|
|
||||||
parentIdentifierGetter, groupGetter,
|
|
||||||
parameterOrAttributeGetters
|
|
||||||
} = transformConfig;
|
|
||||||
|
|
||||||
// Set name and protocol
|
|
||||||
const name = nameGetter(row);
|
const name = nameGetter(row);
|
||||||
const protocol = protocolGetter(row);
|
const protocol = protocolGetter(row);
|
||||||
|
|
||||||
// Set the parent group ID and/or group path
|
// Get any users or user groups who should be granted access
|
||||||
|
const users = usersGetter(row);
|
||||||
|
const groups = userGroupsGetter(row);
|
||||||
|
|
||||||
|
// Get the parent group ID and/or group path
|
||||||
const group = groupGetter && groupGetter(row);
|
const group = groupGetter && groupGetter(row);
|
||||||
const parentIdentifier = (
|
const parentIdentifier = (
|
||||||
parentIdentifierGetter && parentIdentifierGetter(row));
|
parentIdentifierGetter && parentIdentifierGetter(row));
|
||||||
|
|
||||||
return {
|
return new ProtoConnection({
|
||||||
|
|
||||||
// Simple fields that are not protocol-specific
|
// Fields that are not protocol-specific
|
||||||
...{
|
...{
|
||||||
name,
|
name,
|
||||||
protocol,
|
protocol,
|
||||||
parentIdentifier,
|
parentIdentifier,
|
||||||
group
|
group,
|
||||||
|
users,
|
||||||
|
groups
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fields that might potentially be either attributes or
|
// Fields that might potentially be either attributes or
|
||||||
@@ -339,7 +431,7 @@ angular.module('import').factory('connectionCSVService',
|
|||||||
|
|
||||||
}, {parameters: {}, attributes: {}})
|
}, {parameters: {}, attributes: {}})
|
||||||
|
|
||||||
}
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -33,6 +33,7 @@ angular.module('import').factory('connectionParseService',
|
|||||||
const Connection = $injector.get('Connection');
|
const Connection = $injector.get('Connection');
|
||||||
const DirectoryPatch = $injector.get('DirectoryPatch');
|
const DirectoryPatch = $injector.get('DirectoryPatch');
|
||||||
const ParseError = $injector.get('ParseError');
|
const ParseError = $injector.get('ParseError');
|
||||||
|
const ParseResult = $injector.get('ParseResult');
|
||||||
const TranslatableMessage = $injector.get('TranslatableMessage');
|
const TranslatableMessage = $injector.get('TranslatableMessage');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
@@ -41,9 +42,82 @@ angular.module('import').factory('connectionParseService',
|
|||||||
const schemaService = $injector.get('schemaService');
|
const schemaService = $injector.get('schemaService');
|
||||||
const connectionCSVService = $injector.get('connectionCSVService');
|
const connectionCSVService = $injector.get('connectionCSVService');
|
||||||
const connectionGroupService = $injector.get('connectionGroupService');
|
const connectionGroupService = $injector.get('connectionGroupService');
|
||||||
|
const userService = $injector.get('userService');
|
||||||
|
const userGroupService = $injector.get('userGroupService');
|
||||||
|
|
||||||
const service = {};
|
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
|
* 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
|
* data is an array, and contains at least one connection entry. Returns an
|
||||||
@@ -127,9 +201,9 @@ angular.module('import').factory('connectionParseService',
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that will resolve to a transformer function that will
|
* Returns a promise that will resolve to a transformer function that will
|
||||||
* take an object that may a "group" field, replacing it if present with a
|
* take an object that may contain a "group" field, replacing it if present
|
||||||
* "parentIdentifier". If both a "group" and "parentIdentifier" field are
|
* with a "parentIdentifier". If both a "group" and "parentIdentifier" field
|
||||||
* present on the provided object, or if no group exists at the specified
|
* are present on the provided object, or if no group exists at the specified
|
||||||
* path, the function will throw a ParseError describing the failure.
|
* path, the function will throw a ParseError describing the failure.
|
||||||
*
|
*
|
||||||
* @returns {Promise.<Function<Object, Object>>}
|
* @returns {Promise.<Function<Object, Object>>}
|
||||||
@@ -170,32 +244,109 @@ angular.module('import').factory('connectionParseService',
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate a given javascript object to a full-fledged Connection
|
/**
|
||||||
const connectionTransformer = connection => new Connection(connection);
|
* Convert a provided ProtoConnection 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
|
||||||
|
* object after being run through all functions in `transformFunctions`.
|
||||||
|
*
|
||||||
|
* @param {Function[]} transformFunctions
|
||||||
|
* An array of transformation functions to run on each entry in
|
||||||
|
* `connection` data.
|
||||||
|
*
|
||||||
|
* @return {Promise.<Object>}
|
||||||
|
* A promise resolving to ParseResult object representing the result of
|
||||||
|
* parsing all provided connection data.
|
||||||
|
*/
|
||||||
|
function parseConnectionData(connectionData, transformFunctions) {
|
||||||
|
|
||||||
// Translate a Connection object to a patch requesting the creation of said
|
// Check that the provided connection data array is not empty
|
||||||
// Connection
|
const checkError = performBasicChecks(connectionData);
|
||||||
const patchTransformer = connection => new DirectoryPatch({
|
if (checkError) {
|
||||||
op: 'add',
|
const deferred = $q.defer();
|
||||||
path: '/',
|
deferred.reject(checkError);
|
||||||
value: connection
|
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}) =>
|
||||||
|
connectionData.reduce((parseResult, data) => {
|
||||||
|
|
||||||
|
const { patches, identifiers, users, groups } = parseResult;
|
||||||
|
|
||||||
|
// Run the array data through each provided transform
|
||||||
|
let connectionObject = data;
|
||||||
|
_.forEach(transformFunctions, transform => {
|
||||||
|
connectionObject = transform(connectionObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
// All errors found while parsing this connection
|
||||||
|
const connectionErrors = [];
|
||||||
|
parseResult.errors.push(connectionErrors);
|
||||||
|
|
||||||
|
// Translate the group on the object to a parentIdentifier
|
||||||
|
try {
|
||||||
|
connectionObject = groupTransformer(connectionObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was a problem with the group or parentIdentifier
|
||||||
|
catch (error) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Translate to a full-fledged Connection
|
||||||
|
const connection = new Connection(connectionObject);
|
||||||
|
|
||||||
|
// Finally, add a patch for creating the connection
|
||||||
|
patches.push(new DirectoryPatch({
|
||||||
|
op: 'add',
|
||||||
|
path: '/',
|
||||||
|
value: connection
|
||||||
|
}));
|
||||||
|
|
||||||
|
// If there are any errors for this connection fail the whole batch
|
||||||
|
if (connectionErrors.length)
|
||||||
|
parseResult.hasErrors = true;
|
||||||
|
|
||||||
|
return parseResult;
|
||||||
|
|
||||||
|
}, new ParseResult()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a provided CSV representation of a connection list into a JSON
|
* Convert a provided CSV representation of a connection list into a JSON
|
||||||
* string to be submitted to the PATCH REST endpoint. The returned JSON
|
* object to be submitted to the PATCH REST endpoint, as well as a list of
|
||||||
* string will contain a PATCH operation to create each connection in the
|
* objects containing lists of user and user group identifiers to be granted
|
||||||
* provided list.
|
* to each connection.
|
||||||
*
|
|
||||||
* TODO: Describe disambiguation suffixes, e.g. hostname (parameter), and
|
|
||||||
* that we will accept without the suffix if it's unambigous. (or not? how about not?)
|
|
||||||
*
|
*
|
||||||
* @param {String} csvData
|
* @param {String} csvData
|
||||||
* The JSON-encoded connection list to convert to a PATCH request body.
|
* The CSV-encoded connection list to process.
|
||||||
*
|
*
|
||||||
* @return {Promise.<Connection[]>}
|
* @return {Promise.<Object>}
|
||||||
* A promise resolving to an array of Connection objects, one for each
|
* A promise resolving to ParseResult object representing the result of
|
||||||
* connection in the provided CSV.
|
* parsing all provided connection data.
|
||||||
*/
|
*/
|
||||||
service.parseCSV = function parseCSV(csvData) {
|
service.parseCSV = function parseCSV(csvData) {
|
||||||
|
|
||||||
@@ -211,133 +362,68 @@ angular.module('import').factory('connectionParseService',
|
|||||||
catch(error) {
|
catch(error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
const deferred = $q.defer();
|
const deferred = $q.defer();
|
||||||
deferred.reject(error);
|
deferred.reject(new ParseError({ message: error.message }));
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slice off the header row to get the data rows
|
|
||||||
const connectionData = parsedData.slice(1);
|
|
||||||
|
|
||||||
// Check that the provided CSV is not empty (the parser always
|
|
||||||
// returns an array)
|
|
||||||
const checkError = performBasicChecks(connectionData);
|
|
||||||
if (checkError) {
|
|
||||||
const deferred = $q.defer();
|
|
||||||
deferred.reject(checkError);
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The header row - an array of string header values
|
// The header row - an array of string header values
|
||||||
const header = parsedData[0];
|
const header = parsedData.length ? parsedData[0] : [];
|
||||||
|
|
||||||
return $q.all({
|
// Slice off the header row to get the data rows
|
||||||
csvTransformer : connectionCSVService.getCSVTransformer(header),
|
const connectionData = parsedData.slice(1);
|
||||||
groupTransformer : getGroupTransformer()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Transform the rows from the CSV file to an array of API patches
|
// Generate the CSV transform function, and apply it to every row
|
||||||
.then(({csvTransformer, groupTransformer}) => connectionData.map(
|
// before applying all the rest of the standard transforms
|
||||||
dataRow => {
|
return connectionCSVService.getCSVTransformer(header).then(
|
||||||
|
csvTransformer =>
|
||||||
|
|
||||||
// Translate the raw CSV data to a javascript object
|
// Apply the CSV transform to every row
|
||||||
let connectionObject = csvTransformer(dataRow);
|
parseConnectionData(connectionData, [csvTransformer]));
|
||||||
|
|
||||||
// Translate the group on the object to a parentIdentifier
|
|
||||||
connectionObject = groupTransformer(connectionObject);
|
|
||||||
|
|
||||||
// Translate to a full-fledged Connection
|
|
||||||
const connection = connectionTransformer(connectionObject);
|
|
||||||
|
|
||||||
// Finally, translate to a patch for creating the connection
|
|
||||||
return patchTransformer(connection);
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a provided YAML representation of a connection list into a JSON
|
* Convert a provided YAML representation of a connection list into a JSON
|
||||||
* string to be submitted to the PATCH REST endpoint. The returned JSON
|
* object to be submitted to the PATCH REST endpoint, as well as a list of
|
||||||
* string will contain a PATCH operation to create each connection in the
|
* objects containing lists of user and user group identifiers to be granted
|
||||||
* provided list.
|
* to each connection.
|
||||||
*
|
*
|
||||||
* @param {String} yamlData
|
* @param {String} yamlData
|
||||||
* The YAML-encoded connection list to convert to a PATCH request body.
|
* The YAML-encoded connection list to process.
|
||||||
*
|
*
|
||||||
* @return {Promise.<Connection[]>}
|
* @return {Promise.<Object>}
|
||||||
* A promise resolving to an array of Connection objects, one for each
|
* A promise resolving to ParseResult object representing the result of
|
||||||
* connection in the provided YAML.
|
* parsing all provided connection data.
|
||||||
*/
|
*/
|
||||||
service.parseYAML = function parseYAML(yamlData) {
|
service.parseYAML = function parseYAML(yamlData) {
|
||||||
|
|
||||||
// Parse from YAML into a javascript array
|
// Parse from YAML into a javascript array
|
||||||
const parsedData = parseYAMLData(yamlData);
|
const connectionData = parseYAMLData(yamlData);
|
||||||
|
|
||||||
// Check that the data is the correct format, and not empty
|
|
||||||
const checkError = performBasicChecks(connectionData);
|
|
||||||
if (checkError) {
|
|
||||||
const deferred = $q.defer();
|
|
||||||
deferred.reject(checkError);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform the data from the YAML file to an array of API patches
|
|
||||||
return getGroupTransformer().then(
|
|
||||||
groupTransformer => parsedData.map(connectionObject => {
|
|
||||||
|
|
||||||
// Translate the group on the object to a parentIdentifier
|
|
||||||
connectionObject = groupTransformer(connectionObject);
|
|
||||||
|
|
||||||
// Translate to a full-fledged Connection
|
|
||||||
const connection = connectionTransformer(connectionObject);
|
|
||||||
|
|
||||||
// Finally, translate to a patch for creating the connection
|
|
||||||
return patchTransformer(connection);
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
// Produce a ParseResult
|
||||||
|
return parseConnectionData(connectionData);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a provided JSON representation of a connection list into a JSON
|
* Convert a provided JSON-encoded representation of a connection list into
|
||||||
* string to be submitted to the PATCH REST endpoint. The returned JSON
|
* an array of patches to be submitted to the PATCH REST endpoint, as well
|
||||||
* string will contain a PATCH operation to create each connection in the
|
* as a list of objects containing lists of user and user group identifiers
|
||||||
* provided list.
|
* to be granted to each connection.
|
||||||
*
|
*
|
||||||
* @param {String} jsonData
|
* @param {String} jsonData
|
||||||
* The JSON-encoded connection list to convert to a PATCH request body.
|
* The JSON-encoded connection list to process.
|
||||||
*
|
*
|
||||||
* @return {Promise.<Connection[]>}
|
* @return {Promise.<Object>}
|
||||||
* A promise resolving to an array of Connection objects, one for each
|
* A promise resolving to ParseResult object representing the result of
|
||||||
* connection in the provided JSON.
|
* parsing all provided connection data.
|
||||||
*/
|
*/
|
||||||
service.parseJSON = function parseJSON(jsonData) {
|
service.parseJSON = function parseJSON(jsonData) {
|
||||||
|
|
||||||
// Parse from JSON into a javascript array
|
// Parse from JSON into a javascript array
|
||||||
const parsedData = JSON.parse(yamlData);
|
const connectionData = JSON.parse(jsonData);
|
||||||
|
|
||||||
// Check that the data is the correct format, and not empty
|
// Produce a ParseResult
|
||||||
const checkError = performBasicChecks(connectionData);
|
return parseConnectionData(connectionData);
|
||||||
if (checkError) {
|
|
||||||
const deferred = $q.defer();
|
|
||||||
deferred.reject(checkError);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform the data from the YAML file to an array of API patches
|
|
||||||
return getGroupTransformer().then(
|
|
||||||
groupTransformer => parsedData.map(connectionObject => {
|
|
||||||
|
|
||||||
// Translate the group on the object to a parentIdentifier
|
|
||||||
connectionObject = groupTransformer(connectionObject);
|
|
||||||
|
|
||||||
// Translate to a full-fledged Connection
|
|
||||||
const connection = connectionTransformer(connectionObject);
|
|
||||||
|
|
||||||
// Finally, translate to a patch for creating the connection
|
|
||||||
return patchTransformer(connection);
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ angular.module('import').factory('ParseError', [function defineParseError() {
|
|||||||
* The object whose properties should be copied within the new
|
* The object whose properties should be copied within the new
|
||||||
* ParseError.
|
* ParseError.
|
||||||
*/
|
*/
|
||||||
var ParseError = function ParseError(template) {
|
const ParseError = function ParseError(template) {
|
||||||
|
|
||||||
// Use empty object by default
|
// Use empty object by default
|
||||||
template = template || {};
|
template = template || {};
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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 ParseResult class.
|
||||||
|
*/
|
||||||
|
angular.module('import').factory('ParseResult', [function defineParseResult() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of parsing a connection import file - containing a list of
|
||||||
|
* API patches ready to be submitted to the PATCH REST API for batch
|
||||||
|
* connection creation, a set of users and user groups to grant access to
|
||||||
|
* each connection, and any errors that may have occured while parsing
|
||||||
|
* each connection.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {ParseResult|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new
|
||||||
|
* ParseResult.
|
||||||
|
*/
|
||||||
|
const ParseResult = function ParseResult(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of patches, ready to be submitted to the PATCH REST API for
|
||||||
|
* batch connection creation.
|
||||||
|
*
|
||||||
|
* @type {DirectoryPatch[]}
|
||||||
|
*/
|
||||||
|
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).
|
||||||
|
*
|
||||||
|
* @type {String[]}
|
||||||
|
*/
|
||||||
|
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 array of errors encountered while parsing the corresponding
|
||||||
|
* connection (at the same array index). Each connection should have a
|
||||||
|
* an array of errors. If empty, no errors occured for this connection.
|
||||||
|
*
|
||||||
|
* @type {ParseError[][]}
|
||||||
|
*/
|
||||||
|
this.errors = template.errors || [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if any errors were encountered while parsing the connections
|
||||||
|
* represented by this ParseResult. This should always be true if there
|
||||||
|
* are a non-zero number of elements in the errors list for any
|
||||||
|
* connection, or false otherwise.
|
||||||
|
*/
|
||||||
|
this.hasErrors = template.hasErrors || false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return ParseResult;
|
||||||
|
|
||||||
|
}]);
|
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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 Connection class.
|
||||||
|
*/
|
||||||
|
angular.module('import').factory('ProtoConnection', [
|
||||||
|
function defineProtoConnection() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a connection to be imported, as parsed from an
|
||||||
|
* user-supplied import file.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Connection|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new
|
||||||
|
* Connection.
|
||||||
|
*/
|
||||||
|
var ProtoConnection = function ProtoConnection(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique identifier of the connection group that contains this
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
this.parentIdentifier = template.parentIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the connection group that contains this connection,
|
||||||
|
* written as e.g. "ROOT/parent/child/group".
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
this.group = template.group;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The human-readable name of this connection, which is not necessarily
|
||||||
|
* unique.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
this.name = template.name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the protocol associated with this connection, such as
|
||||||
|
* "vnc" or "rdp".
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
this.protocol = template.protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection configuration parameters, as dictated by the protocol in
|
||||||
|
* use, arranged as name/value pairs. This information may not be
|
||||||
|
* available until directly queried. If this information is
|
||||||
|
* unavailable, this property will be null or undefined.
|
||||||
|
*
|
||||||
|
* @type Object.<String, String>
|
||||||
|
*/
|
||||||
|
this.parameters = template.parameters || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arbitrary name/value pairs which further describe this connection.
|
||||||
|
* The semantics and validity of these attributes are dictated by the
|
||||||
|
* extension which defines them.
|
||||||
|
*
|
||||||
|
* @type Object.<String, String>
|
||||||
|
*/
|
||||||
|
this.attributes = template.attributes || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifiers of all users who should be granted read access to
|
||||||
|
* this connection.
|
||||||
|
*
|
||||||
|
* @type String[]
|
||||||
|
*/
|
||||||
|
this.users = template.users || [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifiers of all user groups who should be granted read access
|
||||||
|
* to this connection.
|
||||||
|
*
|
||||||
|
* @type String[]
|
||||||
|
*/
|
||||||
|
this.groups = template.groups || [];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProtoConnection;
|
||||||
|
|
||||||
|
}]);
|
@@ -200,6 +200,10 @@
|
|||||||
"ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found",
|
"ERROR_INVALID_GROUP": "No group matching \"{GROUP}\" found",
|
||||||
"ERROR_INVALID_FILE_TYPE":
|
"ERROR_INVALID_FILE_TYPE":
|
||||||
"Invalid import file type \"{TYPE}\"",
|
"Invalid import file type \"{TYPE}\"",
|
||||||
|
"ERROR_INVALID_USER_IDENTIFIERS":
|
||||||
|
"Users not found: {IDENTIFIER_LIST}",
|
||||||
|
"ERROR_INVALID_USER_GROUP_IDENTIFIERS":
|
||||||
|
"User Groups not found: {IDENTIFIER_LIST}",
|
||||||
"ERROR_NO_FILE_SUPPLIED": "Please select a file to import",
|
"ERROR_NO_FILE_SUPPLIED": "Please select a file to import",
|
||||||
"ERROR_AMBIGUOUS_PARENT_GROUP":
|
"ERROR_AMBIGUOUS_PARENT_GROUP":
|
||||||
"Both group and parentIdentifier may be not specified at the same time",
|
"Both group and parentIdentifier may be not specified at the same time",
|
||||||
|
Reference in New Issue
Block a user