mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-926: Merge further batch import fixes and improvements
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
|
||||
values[type][name] = value;
|
||||
// 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",
|
||||
|
||||
@@ -225,12 +231,12 @@
|
||||
"FIELD_HEADER_EXISTING_PERMISSION_MODE" : "Reset permissions",
|
||||
|
||||
"HELP_CSV_DESCRIPTION" : "A connection import CSV file has one connection record per row. Each column will specify a connection field. At minimum the connection name and protocol must be specified.",
|
||||
"HELP_CSV_EXAMPLE" : "name,protocol,hostname,group,users,groups,guacd-encryption (attribute)\nconn1,vnc,conn1.web.com,ROOT,guac user 1;guac user 2,Connection 1 Users,none\nconn2,rdp,conn2.web.com,ROOT/Parent Group,guac user 1,,ssl\nconn3,ssh,conn3.web.com,ROOT/Parent Group/Child Group,guac user 2;guac user 3,,\nconn4,kubernetes,,,,,",
|
||||
"HELP_CSV_EXAMPLE" : "name,protocol,username,password,hostname,group,users,groups,guacd-encryption (attribute)\nconn1,vnc,alice,pass1,conn1.web.com,ROOT,guac user 1;guac user 2,Connection 1 Users,none\nconn2,rdp,bob,pass2,conn2.web.com,ROOT/Parent Group,guac user 1,,ssl\nconn3,ssh,carol,pass3,conn3.web.com,ROOT/Parent Group/Child Group,guac user 2;guac user 3,,\nconn4,kubernetes,,,,,,,",
|
||||
"HELP_CSV_MORE_DETAILS" : "The CSV header for each row specifies the connection field. The connection group ID that the connection should be imported into may be directly specified with \"parentIdentifier\", or the path to the parent group may be specified using \"group\" as shown below. In most cases, there should be no conflict between fields, but if needed, an \" (attribute)\" or \" (parameter)\" suffix may be added to disambiguate. Lists of user or user group identifiers must be semicolon-separated.¹",
|
||||
"HELP_FILE_TYPE_DESCRIPTION" : "Three file types are supported for connection import: CSV, JSON, and YAML. The same data may be specified by each file type. This must include the connection name and protocol. Optionally, a connection group location, a list of users and/or user groups to grant access, connection parameters, or connection protocols may also be specified. Any users or user groups that do not exist in the current data source will be automatically created. Note that any existing connection permissions will not be removed for updated connections, unless \"Reset permissions\" is checked.",
|
||||
"HELP_FILE_TYPE_HEADER" : "File Types",
|
||||
"HELP_JSON_DESCRIPTION" : "A connection import JSON file is a list of connection objects. At minimum the connection name and protocol must be specified in each connection object.",
|
||||
"HELP_JSON_EXAMPLE" : "[\n \\{\n \"name\": \"conn1\",\n \"protocol\": \"vnc\",\n \"parameters\": \\{ \"hostname\": \"conn1.web.com\" \\},\n \"parentIdentifier\": \"ROOT\",\n \"users\": [ \"guac user 1\", \"guac user 2\" ],\n \"groups\": [ \"Connection 1 Users\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn2\",\n \"protocol\": \"rdp\",\n \"parameters\": \\{ \"hostname\": \"conn2.web.com\" \\},\n \"group\": \"ROOT/Parent Group\",\n \"users\": [ \"guac user 1\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn3\",\n \"protocol\": \"ssh\",\n \"parameters\": \\{ \"hostname\": \"conn3.web.com\" \\},\n \"group\": \"ROOT/Parent Group/Child Group\",\n \"users\": [ \"guac user 2\", \"guac user 3\" ]\n \\},\n \\{\n \"name\": \"conn4\",\n \"protocol\": \"kubernetes\"\n \\}\n]",
|
||||
"HELP_JSON_EXAMPLE" : "[\n \\{\n \"name\": \"conn1\",\n \"protocol\": \"vnc\",\n \"parameters\": \\{ \"username\": \"alice\", \"password\": \"pass1\", \"hostname\": \"conn1.web.com\" \\},\n \"parentIdentifier\": \"ROOT\",\n \"users\": [ \"guac user 1\", \"guac user 2\" ],\n \"groups\": [ \"Connection 1 Users\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn2\",\n \"protocol\": \"rdp\",\n \"parameters\": \\{ \"username\": \"bob\", \"password\": \"pass2\", \"hostname\": \"conn2.web.com\" \\},\n \"group\": \"ROOT/Parent Group\",\n \"users\": [ \"guac user 1\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn3\",\n \"protocol\": \"ssh\",\n \"parameters\": \\{ \"username\": \"carol\", \"password\": \"pass3\", \"hostname\": \"conn3.web.com\" \\},\n \"group\": \"ROOT/Parent Group/Child Group\",\n \"users\": [ \"guac user 2\", \"guac user 3\" ]\n \\},\n \\{\n \"name\": \"conn4\",\n \"protocol\": \"kubernetes\"\n \\}\n]",
|
||||
"HELP_JSON_MORE_DETAILS" : "The connection group ID that the connection should be imported into may be directly specified with a \"parentIdentifier\" field, or the path to the parent group may be specified using a \"group\" field as shown below. An array of user and user group identifiers to grant access to may be specified per connection.",
|
||||
"HELP_EXISTING_CONNECTION_MODE" : "Entirely replace/update existing connections if their names and parent connection groups match the values in the provided file. If unchecked, attempting to import a connection with the same name and parent connection group of an existing connection will be considered an error.",
|
||||
"HELP_EXISTING_PERMISSION_MODE" : "Fully reset the permissions granted for all connections in the provided file to the permissions specified in that file. If no permissions are specified, all relevant connection permissions will be revoked. If unchecked, existing permissions are preserved, and any permissions specified in the file will be added.",
|
||||
@@ -238,7 +244,7 @@
|
||||
"HELP_UPLOAD_DROP_TITLE" : "Drop a File Here",
|
||||
"HELP_UPLOAD_FILE_TYPES" : "CSV, JSON, or YAML",
|
||||
"HELP_YAML_DESCRIPTION" : "A connection import YAML file is a list of connection objects with exactly the same structure as the JSON format.",
|
||||
"HELP_YAML_EXAMPLE" : "---\n - name: conn1\n protocol: vnc\n parameters:\n hostname: conn1.web.com\n group: ROOT\n users:\n - guac user 1\n - guac user 2\n groups:\n - Connection 1 Users\n attributes:\n guacd-encryption: none\n - name: conn2\n protocol: rdp\n parameters:\n hostname: conn2.web.com\n group: ROOT/Parent Group\n users:\n - guac user 1\n attributes:\n guacd-encryption: none\n - name: conn3\n protocol: ssh\n parameters:\n hostname: conn3.web.com\n group: ROOT/Parent Group/Child Group\n users:\n - guac user 2\n - guac user 3\n - name: conn4\n protocol: kubernetes",
|
||||
"HELP_YAML_EXAMPLE" : "---\n - name: conn1\n protocol: vnc\n parameters:\n username: alice\n password: pass1\n hostname: conn1.web.com\n group: ROOT\n users:\n - guac user 1\n - guac user 2\n groups:\n - Connection 1 Users\n attributes:\n guacd-encryption: none\n - name: conn2\n protocol: rdp\n parameters:\n username: bob\n password: pass2\n hostname: conn2.web.com\n group: ROOT/Parent Group\n users:\n - guac user 1\n attributes:\n guacd-encryption: none\n - name: conn3\n protocol: ssh\n parameters:\n username: carol\n password: pass3\n hostname: conn3.web.com\n group: ROOT/Parent Group/Child Group\n users:\n - guac user 2\n - guac user 3\n - name: conn4\n protocol: kubernetes",
|
||||
|
||||
"INFO_CONNECTIONS_IMPORTED_SUCCESS" : "{NUMBER} {NUMBER, plural, one{connection} other{connections}} imported successfully.",
|
||||
|
||||
|
Reference in New Issue
Block a user