mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-926: Improve error handling for invalid, missing connection data.
This commit is contained in:
@@ -322,10 +322,32 @@ angular.module('import').factory('connectionCSVService',
|
||||
// of the header
|
||||
const value = fetchFieldAtIndex(row);
|
||||
|
||||
// If no value is provided, do not check the validity
|
||||
// of the parameter/attribute. Doing so would prevent
|
||||
// the import of a list of mixed protocol types, where
|
||||
// fields are only populated for protocols for which
|
||||
// they are valid parameters. If a value IS provided,
|
||||
// it must be a valid parameter or attribute for the
|
||||
// current protocol, which will be checked below.
|
||||
if (!value)
|
||||
return {};
|
||||
|
||||
// The protocol may determine whether a field is
|
||||
// a parameter or an attribute (or both)
|
||||
const protocol = transformConfig.protocolGetter(row);
|
||||
|
||||
// Any errors encountered while processing this row
|
||||
const errors = [];
|
||||
|
||||
// Before checking whether it's an attribute or protocol,
|
||||
// make sure this is a valid protocol to start
|
||||
if (!protocolParameters[protocol])
|
||||
|
||||
// If the protocol is invalid, do not throw an error
|
||||
// here - this will be handled further downstream
|
||||
// by non-CSV-specific error handling
|
||||
return {};
|
||||
|
||||
// Determine if the field refers to an attribute or a
|
||||
// parameter (or both, which is an error)
|
||||
const isAttribute = !!attributes[name];
|
||||
@@ -336,24 +358,24 @@ angular.module('import').factory('connectionCSVService',
|
||||
// parameter with the provided name, it's impossible to
|
||||
// figure out which this should be
|
||||
if (isAttribute && isParameter)
|
||||
throw new ParseError({
|
||||
errors.push(new ParseError({
|
||||
message: 'Ambiguous CSV Header: ' + header,
|
||||
key: 'IMPORT.ERROR_AMBIGUOUS_CSV_HEADER',
|
||||
variables: { HEADER: header }
|
||||
});
|
||||
}));
|
||||
|
||||
// It's neither an attribute or a parameter
|
||||
else if (!isAttribute && !isParameter)
|
||||
throw new ParseError({
|
||||
errors.push(new ParseError({
|
||||
message: 'Invalid CSV Header: ' + header,
|
||||
key: 'IMPORT.ERROR_INVALID_CSV_HEADER',
|
||||
variables: { HEADER: header }
|
||||
});
|
||||
}));
|
||||
|
||||
// Choose the appropriate type
|
||||
const type = isAttribute ? 'attributes' : 'parameters';
|
||||
|
||||
return { type, name, value };
|
||||
return { type, name, value, errors };
|
||||
});
|
||||
});
|
||||
|
||||
@@ -364,19 +386,20 @@ angular.module('import').factory('connectionCSVService',
|
||||
parameterOrAttributeGetters
|
||||
} = transformConfig;
|
||||
|
||||
// Fail if the name wasn't provided
|
||||
// Fail if the name wasn't provided. Note that this is a file-level
|
||||
// error, not specific to any connection.
|
||||
if (!nameGetter)
|
||||
return deferred.reject(new ParseError({
|
||||
throw new ParseError({
|
||||
message: 'The connection name must be provided',
|
||||
key: 'IMPORT.ERROR_REQUIRED_NAME'
|
||||
}));
|
||||
key: 'IMPORT.ERROR_REQUIRED_NAME_FILE'
|
||||
});
|
||||
|
||||
// Fail if the protocol wasn't provided
|
||||
if (!protocolGetter)
|
||||
return deferred.reject(new ParseError({
|
||||
throw new ParseError({
|
||||
message: 'The connection protocol must be provided',
|
||||
key: 'IMPORT.ERROR_REQUIRED_PROTOCOL'
|
||||
}));
|
||||
key: 'IMPORT.ERROR_REQUIRED_PROTOCOL_FILE'
|
||||
});
|
||||
|
||||
// The function to transform a CSV row into a connection object
|
||||
deferred.resolve(function transformCSVRow(row) {
|
||||
@@ -409,14 +432,20 @@ angular.module('import').factory('connectionCSVService',
|
||||
...parameterOrAttributeGetters.reduce((values, getter) => {
|
||||
|
||||
// Determine the type, name, and value
|
||||
const { type, name, value } = getter(row);
|
||||
const { type, name, value, errors } = getter(row);
|
||||
|
||||
// Set the value and continue on to the next attribute
|
||||
// or parameter
|
||||
// Set the value if available
|
||||
if (type && name && value)
|
||||
values[type][name] = value;
|
||||
|
||||
// If there were errors
|
||||
if (errors && errors.length)
|
||||
values.errors = [...values.errors, ...errors];
|
||||
|
||||
// Continue on to the next attribute or parameter
|
||||
return values;
|
||||
|
||||
}, {parameters: {}, attributes: {}})
|
||||
}, {parameters: {}, attributes: {}, errors: []})
|
||||
|
||||
});
|
||||
|
||||
|
@@ -271,6 +271,13 @@ angular.module('import').factory('connectionParseService',
|
||||
// to be translated into an absolute path starting at the root
|
||||
group = connection.group;
|
||||
|
||||
// If the provided group isn't a string, it can never be valid
|
||||
if (typeof group !== 'string')
|
||||
throw new ParseError({
|
||||
message: 'Invalid group type - must be a string',
|
||||
key: 'IMPORT.ERROR_INVALID_GROUP_TYPE'
|
||||
});
|
||||
|
||||
// Allow the group to start with a leading slash instead instead
|
||||
// of explicitly requiring the root connection group
|
||||
if (group.startsWith('/'))
|
||||
@@ -351,6 +358,84 @@ angular.module('import').factory('connectionParseService',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves to a map of all valid protocols to the
|
||||
* boolean value "true", i.e. a set of all valid protocols.
|
||||
*
|
||||
* @returns {Promise.<Object.<String, Boolean>>}
|
||||
* A promise that resolves to a set of all valid protocols.
|
||||
*/
|
||||
function getValidProtocols() {
|
||||
|
||||
// The current data source - the one that the connections will be
|
||||
// imported into
|
||||
const dataSource = $routeParams.dataSource;
|
||||
|
||||
// Fetch the protocols and convert to a set of valid protocol names
|
||||
return schemaService.getProtocols(dataSource).then(
|
||||
protocols => _.mapValues(protocols, () => true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of field-level errors for the provided connection,
|
||||
* such as missing or invalid fields that are not dependant on the
|
||||
* connection group heirarchy.
|
||||
*
|
||||
* @param {ImportConnection} connection
|
||||
* The connection object to check field values on.
|
||||
*
|
||||
* @param {Object.<String, Boolean>} protocols
|
||||
* A set of valid protocols, such as the one returned by
|
||||
* getValidProtocols().
|
||||
*
|
||||
* @returns {ParseError[]}
|
||||
* A list of field-level errors for the provided connection.
|
||||
*/
|
||||
function getFieldErrors(connection, protocols) {
|
||||
const connectionErrors = [];
|
||||
|
||||
// Ensure that a protocol was specified for this connection
|
||||
const protocol = connection.protocol;
|
||||
if (!protocol)
|
||||
connectionErrors.push(new ParseError({
|
||||
message: 'Missing required protocol field',
|
||||
key: 'IMPORT.ERROR_REQUIRED_PROTOCOL_CONNECTION'
|
||||
}));
|
||||
|
||||
// Ensure that a valid protocol was specified for this connection
|
||||
if (!protocols[protocol])
|
||||
connectionErrors.push(new ParseError({
|
||||
message: 'Invalid protocol: ' + protocol,
|
||||
key: 'IMPORT.ERROR_INVALID_PROTOCOL',
|
||||
variables: { PROTOCOL: protocol }
|
||||
}));
|
||||
|
||||
// Ensure that a name was specified for this connection
|
||||
if (!connection.name)
|
||||
connectionErrors.push(new ParseError({
|
||||
message: 'Missing required name field',
|
||||
key: 'IMPORT.ERROR_REQUIRED_NAME_CONNECTION'
|
||||
}));
|
||||
|
||||
// Ensure that the specified user list, if any, is an array
|
||||
const users = connection.users;
|
||||
if (users && !Array.isArray(users))
|
||||
connectionErrors.push(new ParseError({
|
||||
message: 'Invalid users list - must be an array',
|
||||
key: 'IMPORT.ERROR_INVALID_USERS_TYPE'
|
||||
}));
|
||||
|
||||
// Ensure that the specified user list, if any, is an array
|
||||
const groups = connection.groups;
|
||||
if (groups && !Array.isArray(groups))
|
||||
connectionErrors.push(new ParseError({
|
||||
message: 'Invalid groups list - must be an array',
|
||||
key: 'IMPORT.ERROR_INVALID_USER_GROUPS_TYPE'
|
||||
}));
|
||||
|
||||
return connectionErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a provided connection array into a ParseResult. Any provided
|
||||
* transform functions will be run on each entry in `connectionData` before
|
||||
@@ -384,8 +469,12 @@ angular.module('import').factory('connectionParseService',
|
||||
|
||||
let index = 0;
|
||||
|
||||
// Get the group transformer to apply to each connection
|
||||
return getTreeTransformer(importConfig).then(treeTransformer =>
|
||||
// Get the tree transformer and valid protocol set
|
||||
return $q.all({
|
||||
treeTransformer : getTreeTransformer(importConfig),
|
||||
protocols : getValidProtocols()
|
||||
})
|
||||
.then(({treeTransformer, protocols}) =>
|
||||
connectionData.reduce((parseResult, data) => {
|
||||
|
||||
const { patches, users, groups, groupPaths } = parseResult;
|
||||
@@ -396,8 +485,13 @@ angular.module('import').factory('connectionParseService',
|
||||
connectionObject = transform(connectionObject);
|
||||
});
|
||||
|
||||
// All errors found while parsing this connection
|
||||
const connectionErrors = [];
|
||||
// All errors encountered while running the connection through the
|
||||
// provided transform, starting with those encountered during
|
||||
// the provided transforms, and any errors from missing fields
|
||||
const connectionErrors = [
|
||||
..._.get(connectionObject, 'errors', []),
|
||||
...getFieldErrors(connectionObject, protocols)
|
||||
];
|
||||
|
||||
// Determine the connection's place in the connection group tree
|
||||
try {
|
||||
|
@@ -114,6 +114,13 @@ angular.module('import').factory('ImportConnection', ['$injector',
|
||||
*/
|
||||
this.importMode = template.importMode || ImportConnection.ImportMode.CREATE;
|
||||
|
||||
/**
|
||||
* Any errors specific to this connection encountered while parsing.
|
||||
*
|
||||
* @type ParseError[]
|
||||
*/
|
||||
this.errors = template.errors || [];
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -211,13 +211,19 @@
|
||||
"ERROR_INVALID_MIME_TYPE" : "Unsupported file type: \"{TYPE}\"",
|
||||
"ERROR_INVALID_GROUP" : "No group matching \"{GROUP}\" found",
|
||||
"ERROR_INVALID_GROUP_IDENTIFIER" : "No connection group with identifier \"{IDENTIFIER}\" found",
|
||||
"ERROR_INVALID_GROUP_TYPE" : "Invalid group - must be a string.",
|
||||
"ERROR_INVALID_PROTOCOL" : "Invalid protocol \"{PROTOCOL}\"",
|
||||
"ERROR_INVALID_USER_GROUPS_TYPE" : "Invalid user groups - must be an array of user group identifiers.",
|
||||
"ERROR_INVALID_USERS_TYPE" : "Invalid users - must be an array of user identifiers.",
|
||||
"ERROR_NO_FILE_SUPPLIED" : "Please select a file to import",
|
||||
"ERROR_PARSE_FAILURE_CSV" : "Please make sure your file is valid CSV. Parsing failed with error \"{ERROR}\". ",
|
||||
"ERROR_PARSE_FAILURE_JSON" : "Please make sure your file is valid JSON. Parsing failed with error \"{ERROR}\". ",
|
||||
"ERROR_PARSE_FAILURE_YAML" : "Please make sure your file is valid YAML. Parsing failed with error \"{ERROR}\". ",
|
||||
"ERROR_REJECT_UPDATE_CONNECTION" : "Connection \"{NAME}\" already exists at \"{PATH}\"",
|
||||
"ERROR_REQUIRED_NAME" : "No connection name found in the provided file",
|
||||
"ERROR_REQUIRED_PROTOCOL" : "No connection protocol found in the provided file",
|
||||
"ERROR_REQUIRED_NAME_CONNECTION" : "The connection name is required",
|
||||
"ERROR_REQUIRED_PROTOCOL_CONNECTION" : "The connection protocol is required",
|
||||
"ERROR_REQUIRED_NAME_FILE" : "No connection name found in the provided file",
|
||||
"ERROR_REQUIRED_PROTOCOL_FILE" : "No connection protocol found in the provided file",
|
||||
|
||||
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
|
||||
|
||||
|
Reference in New Issue
Block a user