GUACAMOLE-926: Fix styling, option semantics, and directory patch endpoint logging/handling.

This commit is contained in:
James Muehlner
2023-04-20 17:40:59 +00:00
parent 896cd48eca
commit 6dab100b42
9 changed files with 82 additions and 156 deletions

View File

@@ -636,43 +636,4 @@ angular.module('import').controller('importConnectionsController', ['$scope', '$
$scope.fileReader.readAsBinaryString(file); $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');
}]); }]);

View File

@@ -312,7 +312,7 @@ angular.module('import').factory('connectionParseService',
throw new ParseError({ throw new ParseError({
message: 'Duplicate connection in file: ' + path, message: 'Duplicate connection in file: ' + path,
key: 'IMPORT.ERROR_DUPLICATE_CONNECTION_IN_FILE', 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 // Mark the current path as already seen in the file
@@ -326,12 +326,12 @@ angular.module('import').factory('connectionParseService',
let identifier; let identifier;
// If updates to existing connections are disallowed // If updates to existing connections are disallowed
if (existingIdentifier && importConfig.replaceConnectionMode === if (existingIdentifier && importConfig.existingConnectionMode ===
ConnectionImportConfig.ReplaceConnectionMode.REJECT) ConnectionImportConfig.ExistingConnectionMode.REJECT)
throw new ParseError({ throw new ParseError({
message: 'Rejecting update to existing connection: ' + path, message: 'Rejecting update to existing connection: ' + path,
key: 'IMPORT.ERROR_REJECT_UPDATE_CONNECTION', 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 // 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 // The connection is being replaced, and permissions are only being
// added, not replaced // added, not replaced
else if (importConfig.existingPermissionMode === else if (importConfig.existingPermissionMode ===
ConnectionImportConfig.ExistingPermissionMode.ADD) ConnectionImportConfig.ExistingPermissionMode.PRESERVE)
// Add a patch for replacing the connection // Add a patch for replacing the connection
patches.push(new DirectoryPatch({ patches.push(new DirectoryPatch({

View File

@@ -171,7 +171,7 @@
.file-upload-container .import-config .help { .file-upload-container .import-config .help {
visibility: hidden; visibility: hidden;
cursor: pointer; cursor: help;
} }

View File

@@ -41,22 +41,22 @@
<ul class="import-config"> <ul class="import-config">
<li> <li>
<input type="checkbox" <input type="checkbox"
id="replace-connections" ng-model="importConfig.replaceConnectionMode" id="existing-connection-mode" ng-model="importConfig.existingConnectionMode"
ng-true-value="'REPLACE'" ng-false-value="'REJECT'" /> ng-true-value="'REPLACE'" ng-false-value="'REJECT'" />
<label for="replace-connections"> <label for="existing-connection-mode">
{{'IMPORT.FIELD_HEADER_REPLACE_CONNECTIONS' | translate}} {{'IMPORT.FIELD_HEADER_EXISTING_CONNECTION_MODE' | translate}}
</label> </label>
<span ng-click="showConnectionReplaceHelp()" class="help"></span> <span ng-attr-title="{{'IMPORT.HELP_EXISTING_CONNECTION_MODE' | translate}}" class="help"></span>
</li> </li>
<li> <li>
<input type="checkbox" <input type="checkbox"
id="replace-permissions" ng-model="importConfig.existingPermissionMode" id="existing-permission-mode" ng-model="importConfig.existingPermissionMode"
ng-disabled="importConfig.replaceConnectionMode === 'REJECT'" ng-disabled="importConfig.replaceConnectionMode === 'REJECT'"
ng-true-value="'REPLACE'" ng-false-value="'ADD'" /> ng-true-value="'REPLACE'" ng-false-value="'PRESERVE'" />
<label for="replace-permissions"> <label for="existing-permission-mode">
{{'IMPORT.FIELD_HEADER_REPLACE_PERMISSIONS' | translate}} {{'IMPORT.FIELD_HEADER_EXISTING_PERMISSION_MODE' | translate}}
</label> </label>
<span ng-click="showPermissionReplaceHelp()" class="help"></span> <span ng-attr-title="{{'IMPORT.HELP_EXISTING_PERMISSION_MODE' | translate}}" class="help"></span>
</li> </li>
</ul> </ul>

View File

@@ -40,10 +40,10 @@ angular.module('import').factory('ConnectionImportConfig', [
/** /**
* The mode for handling connections that match existing connections. * The mode for handling connections that match existing connections.
* *
* @type ConnectionImportConfig.ReplaceConnectionMode * @type ConnectionImportConfig.ExistingConnectionMode
*/ */
this.replaceConnectionMode = template.replaceConnectionMode this.existingConnectionMode = template.existingConnectionMode
|| ConnectionImportConfig.ReplaceConnectionMode.REJECT; || ConnectionImportConfig.ExistingConnectionMode.REJECT;
/** /**
* The mode for handling permissions on existing connections that are * The mode for handling permissions on existing connections that are
@@ -53,19 +53,19 @@ angular.module('import').factory('ConnectionImportConfig', [
* @type ConnectionImportConfig.ExistingPermissionMode * @type ConnectionImportConfig.ExistingPermissionMode
*/ */
this.existingPermissionMode = template.existingPermissionMode this.existingPermissionMode = template.existingPermissionMode
|| ConnectionImportConfig.ExistingPermissionMode.ADD; || ConnectionImportConfig.ExistingPermissionMode.PRESERVE;
}; };
/** /**
* Valid modes for the behavior of the importer when attempts are made to * Valid modes for the behavior of the importer when an imported connection
* update existing connections. * already exists.
*/ */
ConnectionImportConfig.ReplaceConnectionMode = { ConnectionImportConfig.ExistingConnectionMode = {
/** /**
* Any attempt to update existing connections will cause the entire * Any Connection that has the same name and parent group as an existing
* import to be rejected with an error. * connection will cause the entire import to be rejected with an error.
*/ */
REJECT : "REJECT", REJECT : "REJECT",
@@ -87,7 +87,7 @@ angular.module('import').factory('ConnectionImportConfig', [
* added to the existing connection, without removing any existing * added to the existing connection, without removing any existing
* permissions. * permissions.
*/ */
ADD : "ADD", PRESERVE : "PRESERVE",
/** /**
* Any existing permissions will be removed, ensuring that only the * Any existing permissions will be removed, ensuring that only the

View File

@@ -80,7 +80,7 @@ angular.module('import').factory('ParseResult', [function defineParseResult() {
/** /**
* An array of errors encountered while parsing the corresponding * An array of errors encountered while parsing the corresponding
* connection (at the same array index in the patches array). Each * 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. * occurred for this connection.
* *
* @type {ParseError[][]} * @type {ParseError[][]}

View File

@@ -1,64 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><g style="stroke-width:1.04766"><g style="font-style:normal;font-weight:400;font-size:142.558px;line-height:100%;font-family:Sans;letter-spacing:0;word-spacing:0;fill:#000;fill-opacity:1;stroke:none;stroke-width:3.20748px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"><path d="M169.301 354.693c-60.575-41.36-30.288-20.68 0 0zm-.627 90.839c-60.157-101.919-30.079-50.96 0 0z" aria-label="!" style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:142.558px;line-height:100%;font-family:Roboto;-inkscape-font-specification:&quot;Roboto Heavy&quot;;text-align:center;text-anchor:middle;fill:#000;stroke-width:3.20748px" transform="matrix(.3118 0 0 .31173 -24.457 -91.229)"/></g><path d="M32 .75A31.25 31.25 0 0 0 .75 32 31.25 31.25 0 0 0 32 63.25 31.25 31.25 0 0 0 63.25 32 31.25 31.25 0 0 0 32 .75Zm0 5A26.25 26.25 0 0 1 58.25 32 26.25 26.25 0 0 1 32 58.25 26.25 26.25 0 0 1 5.75 32 26.25 26.25 0 0 1 32 5.75Z" style="fill:#000"/><g style="font-size:40px;line-height:1.25"><path d="M27.775 45.836h5.551v6.945h-5.55zm5.387-4.02H27.94v-4.21q0-2.762.766-4.54.766-1.777 3.227-4.129l2.46-2.433q1.56-1.45 2.243-2.734.71-1.286.71-2.625 0-2.434-1.804-3.938-1.777-1.504-4.73-1.504-2.16 0-4.622.957-2.433.957-5.085 2.79v-5.141q2.57-1.559 5.195-2.325 2.652-.765 5.469-.765 5.03 0 8.066 2.652 3.062 2.652 3.062 7 0 2.078-.984 3.965-.984 1.86-3.445 4.21l-2.406 2.352q-1.286 1.286-1.832 2.024-.52.71-.739 1.394-.164.575-.246 1.395-.082.82-.082 2.242z" aria-label="?" style="font-size:56px;-inkscape-font-specification:&quot;sans-serif, Normal&quot;"/></g></g></svg>
<svg
width="64"
height="64"
viewBox="0 0 64 64"
version="1.1"
id="svg10"
sodipodi:docname="question.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs14" />
<sodipodi:namedview
id="namedview12"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="8"
inkscape:cx="38.5625"
inkscape:cy="31.0625"
inkscape:window-width="2048"
inkscape:window-height="1025"
inkscape:window-x="0"
inkscape:window-y="28"
inkscape:window-maximized="1"
inkscape:current-layer="g8" />
<g
style="stroke-width:1.04766"
id="g8">
<g
style="font-style:normal;font-weight:400;font-size:142.558px;line-height:100%;font-family:Sans;letter-spacing:0;word-spacing:0;fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.20748px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="g6">
<path
d="m 169.301,354.693 c -60.57527,-41.3594 -30.28763,-20.6797 0,0 z m -0.627,90.839 c -60.15727,-101.91873 -30.07863,-50.95937 0,0 z"
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:142.558px;line-height:100%;font-family:Roboto;-inkscape-font-specification:'Roboto Heavy';text-align:center;text-anchor:middle;fill:#000000;stroke-width:3.20748px"
transform="matrix(0.3118,0,0,0.31173,-24.457,-91.229)"
aria-label="!"
id="path4"
sodipodi:nodetypes="cccc" />
</g>
<path
id="path2540"
style="fill:#000000"
d="M 32,0.75 A 31.25,31.25 0 0 0 0.75,32 31.25,31.25 0 0 0 32,63.25 31.25,31.25 0 0 0 63.25,32 31.25,31.25 0 0 0 32,0.75 Z m 0,5 A 26.25,26.25 0 0 1 58.25,32 26.25,26.25 0 0 1 32,58.25 26.25,26.25 0 0 1 5.75,32 26.25,26.25 0 0 1 32,5.75 Z" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
x="17.083984"
y="52.78125"
id="text4900"><tspan
id="tspan4898"
x="17.083984"
y="52.78125"
sodipodi:role="line"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:56px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal">?</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -204,7 +204,7 @@
"ERROR_AMBIGUOUS_PARENT_GROUP" : "Both group and parentIdentifier may be not specified at the same time", "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_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_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_DUPLICATE_CSV_HEADER" : "Duplicate CSV Header: {HEADER}",
"ERROR_EMPTY_FILE" : "The provided file is empty", "ERROR_EMPTY_FILE" : "The provided file is empty",
"ERROR_INVALID_CSV_HEADER" : "Invalid CSV Header \"{HEADER}\" is neither an attribute or parameter", "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_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_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_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_NAME" : "No connection name found in the provided file",
"ERROR_REQUIRED_PROTOCOL" : "No connection protocol found in the provided file", "ERROR_REQUIRED_PROTOCOL" : "No connection protocol found in the provided file",
"FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER",
"FIELD_HEADER_REPLACE_CONNECTIONS" : "Replace/Update existing connections", "FIELD_HEADER_EXISTING_CONNECTION_MODE" : "Replace/Update existing connections",
"FIELD_HEADER_REPLACE_PERMISSIONS" : "Reset permissions", "FIELD_HEADER_EXISTING_PERMISSION_MODE" : "Reset permissions",
"FIELD_OPTION_DUPLICATE_REPLACE" : "Replace duplicates",
"FIELD_OPTION_DUPLICATE_IGNORE" : "Ignore duplicates",
"FIELD_OPTION_DUPLICATE_ERROR" : "Disallow duplicates",
"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_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,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_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_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_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\": \\{ \"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_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_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_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_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_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_SEMICOLON_FOOTNOTE" : "If present, semicolons can be escaped with a backslash, e.g. \"first\\\\;last\"", "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_DROP_TITLE" : "Drop a File Here",
"HELP_UPLOAD_FILE_TYPES" : "CSV, JSON, or YAML", "HELP_UPLOAD_FILE_TYPES" : "CSV, JSON, or YAML",

View File

@@ -421,6 +421,45 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
} }
/**
* Retrieve and return the object having the given identifier from the
* directory, throwing a GuacamoleResourceNotFoundException and firing a
* directory GET failure event if no object exists with the given identifier
* in the directory.
*
* @param identifier
* The identifier of the object to retrieve from the directory.
*
* @return
* The object from the directory with the provided identifier.
*
* @throws GuacamoleException
* If no object with the provided identifier exists within the
* directory, or if any other error occurs while attempting to retrieve
* the object.
*/
@Nonnull
private InternalType getObjectByIdentifier(String identifier)
throws GuacamoleException {
// Retrieve the object having the given identifier
InternalType object;
try {
object = directory.get(identifier);
if (object == null)
throw new GuacamoleResourceNotFoundException(
"Not found: \"" + identifier + "\"");
}
catch (GuacamoleException | RuntimeException | Error e) {
fireDirectoryFailureEvent(
DirectoryEvent.Operation.GET, identifier, null, e);
throw e;
}
// Return the object; it is guaranteed to be non-null at this point
return object;
}
/** /**
* If the provided throwable is a known Guacamole-specific type, create and * If the provided throwable is a known Guacamole-specific type, create and
* return a APIPatchError with an error message extracted from the error. * return a APIPatchError with an error message extracted from the error.
@@ -600,8 +639,10 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
try { try {
// Fetch the object to be updated // Fetch the object to be updated. If no object is
original = directory.get(identifier); // found, a directory GET failure event will be
// logged, and the update attempt will be aborted.
original = getObjectByIdentifier(identifier);
// Apply the changes to the original object // Apply the changes to the original object
translator.applyExternalChanges( translator.applyExternalChanges(
@@ -805,17 +846,10 @@ public abstract class DirectoryResource<InternalType extends Identifiable, Exter
getObjectResource(@PathParam("identifier") String identifier) getObjectResource(@PathParam("identifier") String identifier)
throws GuacamoleException { throws GuacamoleException {
// Retrieve the object having the given identifier // Fetch the object to be updated. If no object is found, a directory
InternalType object; // GET failure event will be logged. If no exception is thrown, the
try { // object is guaranteed to exist
object = directory.get(identifier); InternalType object = getObjectByIdentifier(identifier);
if (object == null)
throw new GuacamoleResourceNotFoundException("Not found: \"" + identifier + "\"");
}
catch (GuacamoleException | RuntimeException | Error e) {
fireDirectoryFailureEvent(DirectoryEvent.Operation.GET, identifier, null, e);
throw e;
}
// Return a resource which provides access to the retrieved object // Return a resource which provides access to the retrieved object
DirectoryObjectResource<InternalType, ExternalType> resource = resourceFactory.create(authenticatedUser, userContext, directory, object); DirectoryObjectResource<InternalType, ExternalType> resource = resourceFactory.create(authenticatedUser, userContext, directory, object);