diff --git a/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js b/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js
index 41f13fb55..cc99e9653 100644
--- a/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js
+++ b/guacamole/src/main/frontend/src/app/import/controllers/importConnectionsController.js
@@ -635,44 +635,5 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
// Read all the data into memory
$scope.fileReader.readAsBinaryString(file);
};
-
- /**
- * Display a modal with the given title and text keys.
- *
- * @param {String} titleKey
- * The translation key to use for the title of the modal.
- *
- * @param {String} contentKey
- * The translation key to use for the text contents of the modal.
- */
- const showModal = (titleKey, contentKey) => guacNotification.showStatus({
-
- // The provided modal contents
- title: titleKey,
- text: { key: contentKey },
-
- // Add a button to hide the modal
- actions : [{
- name : 'IMPORT.ACTION_ACKNOWLEDGE',
- callback : () => guacNotification.showStatus(false)
- }]
-
- });
-
- /**
- * Display a modal with information about the existing connection
- * replacement option.
- */
- $scope.showConnectionReplaceHelp = () => showModal(
- 'IMPORT.HELP_REPLACE_CONNECTION_TITLE',
- 'IMPORT.HELP_REPLACE_CONNECTION_CONTENT');
-
- /**
- * Display a modal with information about the existing connection permission
- * replacement option.
- */
- $scope.showPermissionReplaceHelp = () => showModal(
- 'IMPORT.HELP_REPLACE_PERMISSION_TITLE',
- 'IMPORT.HELP_REPLACE_PERMISSION_CONTENT');
-
+
}]);
diff --git a/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js b/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js
index 90d1bf27b..c5765c2c7 100644
--- a/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js
+++ b/guacamole/src/main/frontend/src/app/import/services/connectionParseService.js
@@ -312,7 +312,7 @@ angular.module('import').factory('connectionParseService',
throw new ParseError({
message: 'Duplicate connection in file: ' + path,
key: 'IMPORT.ERROR_DUPLICATE_CONNECTION_IN_FILE',
- variables: { PATH: path }
+ variables: { NAME: connection.name, PATH: group }
});
// Mark the current path as already seen in the file
@@ -326,12 +326,12 @@ angular.module('import').factory('connectionParseService',
let identifier;
// If updates to existing connections are disallowed
- if (existingIdentifier && importConfig.replaceConnectionMode ===
- ConnectionImportConfig.ReplaceConnectionMode.REJECT)
+ if (existingIdentifier && importConfig.existingConnectionMode ===
+ ConnectionImportConfig.ExistingConnectionMode.REJECT)
throw new ParseError({
message: 'Rejecting update to existing connection: ' + path,
key: 'IMPORT.ERROR_REJECT_UPDATE_CONNECTION',
- variables: { PATH: path }
+ variables: { NAME: connection.name, PATH: group }
});
// If the connection is being replaced, set the existing identifer
@@ -430,7 +430,7 @@ angular.module('import').factory('connectionParseService',
// The connection is being replaced, and permissions are only being
// added, not replaced
else if (importConfig.existingPermissionMode ===
- ConnectionImportConfig.ExistingPermissionMode.ADD)
+ ConnectionImportConfig.ExistingPermissionMode.PRESERVE)
// Add a patch for replacing the connection
patches.push(new DirectoryPatch({
diff --git a/guacamole/src/main/frontend/src/app/import/styles/import.css b/guacamole/src/main/frontend/src/app/import/styles/import.css
index 36e63d69b..5e2cd71e1 100644
--- a/guacamole/src/main/frontend/src/app/import/styles/import.css
+++ b/guacamole/src/main/frontend/src/app/import/styles/import.css
@@ -171,7 +171,7 @@
.file-upload-container .import-config .help {
visibility: hidden;
- cursor: pointer;
+ cursor: help;
}
diff --git a/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html b/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html
index 2d3b8abc7..2af6cad7f 100644
--- a/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html
+++ b/guacamole/src/main/frontend/src/app/import/templates/connectionImport.html
@@ -41,22 +41,22 @@
diff --git a/guacamole/src/main/frontend/src/app/import/types/ConnectionImportConfig.js b/guacamole/src/main/frontend/src/app/import/types/ConnectionImportConfig.js
index fa7ce8118..fb0673055 100644
--- a/guacamole/src/main/frontend/src/app/import/types/ConnectionImportConfig.js
+++ b/guacamole/src/main/frontend/src/app/import/types/ConnectionImportConfig.js
@@ -40,10 +40,10 @@ angular.module('import').factory('ConnectionImportConfig', [
/**
* The mode for handling connections that match existing connections.
*
- * @type ConnectionImportConfig.ReplaceConnectionMode
+ * @type ConnectionImportConfig.ExistingConnectionMode
*/
- this.replaceConnectionMode = template.replaceConnectionMode
- || ConnectionImportConfig.ReplaceConnectionMode.REJECT;
+ this.existingConnectionMode = template.existingConnectionMode
+ || ConnectionImportConfig.ExistingConnectionMode.REJECT;
/**
* The mode for handling permissions on existing connections that are
@@ -53,19 +53,19 @@ angular.module('import').factory('ConnectionImportConfig', [
* @type ConnectionImportConfig.ExistingPermissionMode
*/
this.existingPermissionMode = template.existingPermissionMode
- || ConnectionImportConfig.ExistingPermissionMode.ADD;
+ || ConnectionImportConfig.ExistingPermissionMode.PRESERVE;
};
/**
- * Valid modes for the behavior of the importer when attempts are made to
- * update existing connections.
+ * Valid modes for the behavior of the importer when an imported connection
+ * already exists.
*/
- ConnectionImportConfig.ReplaceConnectionMode = {
+ ConnectionImportConfig.ExistingConnectionMode = {
/**
- * Any attempt to update existing connections will cause the entire
- * import to be rejected with an error.
+ * Any Connection that has the same name and parent group as an existing
+ * connection will cause the entire import to be rejected with an error.
*/
REJECT : "REJECT",
@@ -87,7 +87,7 @@ angular.module('import').factory('ConnectionImportConfig', [
* added to the existing connection, without removing any existing
* permissions.
*/
- ADD : "ADD",
+ PRESERVE : "PRESERVE",
/**
* Any existing permissions will be removed, ensuring that only the
diff --git a/guacamole/src/main/frontend/src/app/import/types/ParseResult.js b/guacamole/src/main/frontend/src/app/import/types/ParseResult.js
index d78ad866e..3d15511cc 100644
--- a/guacamole/src/main/frontend/src/app/import/types/ParseResult.js
+++ b/guacamole/src/main/frontend/src/app/import/types/ParseResult.js
@@ -80,7 +80,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
/**
* An array of errors encountered while parsing the corresponding
* connection (at the same array index in the patches array). Each
- * connection should have a an array of errors. If empty, no errors
+ * connection should have an array of errors. If empty, no errors
* occurred for this connection.
*
* @type {ParseError[][]}
diff --git a/guacamole/src/main/frontend/src/images/question.svg b/guacamole/src/main/frontend/src/images/question.svg
index e3c800f85..062ccfa35 100644
--- a/guacamole/src/main/frontend/src/images/question.svg
+++ b/guacamole/src/main/frontend/src/images/question.svg
@@ -1,64 +1 @@
-
-
+
\ No newline at end of file
diff --git a/guacamole/src/main/frontend/src/translations/en.json b/guacamole/src/main/frontend/src/translations/en.json
index 1aeea1df6..972bab93d 100644
--- a/guacamole/src/main/frontend/src/translations/en.json
+++ b/guacamole/src/main/frontend/src/translations/en.json
@@ -204,7 +204,7 @@
"ERROR_AMBIGUOUS_PARENT_GROUP" : "Both group and parentIdentifier may be not specified at the same time",
"ERROR_ARRAY_REQUIRED" : "The provided file must contain a list of connections",
"ERROR_DETECTED_INVALID_TYPE" : "Unsupported file type. Please make sure the file is valid CSV, JSON, or YAML.",
- "ERROR_DUPLICATE_CONNECTION_IN_FILE" : "Duplicate connection in file at \"{PATH}\"",
+ "ERROR_DUPLICATE_CONNECTION_IN_FILE" : "Duplicated connection \"{NAME}\" at \"{PATH}\" in import file",
"ERROR_DUPLICATE_CSV_HEADER" : "Duplicate CSV Header: {HEADER}",
"ERROR_EMPTY_FILE" : "The provided file is empty",
"ERROR_INVALID_CSV_HEADER" : "Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter",
@@ -215,31 +215,25 @@
"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" : "Disallowed update to existing connection at \"{PATH}\"",
+ "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",
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
- "FIELD_HEADER_REPLACE_CONNECTIONS" : "Replace/Update existing connections",
- "FIELD_HEADER_REPLACE_PERMISSIONS" : "Reset permissions",
-
- "FIELD_OPTION_DUPLICATE_REPLACE" : "Replace duplicates",
- "FIELD_OPTION_DUPLICATE_IGNORE" : "Ignore duplicates",
- "FIELD_OPTION_DUPLICATE_ERROR" : "Disallow duplicates",
+ "FIELD_HEADER_EXISTING_CONNECTION_MODE" : "Replace/Update existing connections",
+ "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_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.",
+ "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_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_REPLACE_CONNECTION_TITLE" : "Replacing existing connections",
- "HELP_REPLACE_CONNECTION_CONTENT" : "Checking this box will allow existing connections to be updated, if an imported connection has the same name and parent group as an existing connection. If unchecked, attempts to update existing connections will be treated as an error.",
- "HELP_REPLACE_PERMISSION_TITLE" : "Replacing connection permissions",
- "HELP_REPLACE_PERMISSION_CONTENT" : "If replacement of existing connections is enabled, checking this box will allow full replacement of connection permissions. If checked, access permission will only be granted to users and groups specified in the import file for this connection. If unchecked, specified users and groups will be granted access in addition to any existing permissions.",
+ "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.",
"HELP_SEMICOLON_FOOTNOTE" : "If present, semicolons can be escaped with a backslash, e.g. \"first\\\\;last\"",
"HELP_UPLOAD_DROP_TITLE" : "Drop a File Here",
"HELP_UPLOAD_FILE_TYPES" : "CSV, JSON, or YAML",
diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java
index 0ea12c3ef..e696f8f30 100644
--- a/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java
+++ b/guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java
@@ -421,6 +421,45 @@ public abstract class DirectoryResource resource = resourceFactory.create(authenticatedUser, userContext, directory, object);