GUACAMOLE-422: Merge support for forwarding client timezone at Guacamole protocol level.

This commit is contained in:
Mike Jumper
2019-06-07 08:53:49 -07:00
committed by GitHub
17 changed files with 519 additions and 9 deletions

View File

@@ -19,7 +19,6 @@
package org.apache.guacamole.protocol;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
@@ -56,6 +55,15 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
*/
private String id;
/**
* The protocol version that will be used to communicate with guacd. The
* default is 1.0.0, and, if the server does not provide a specific version
* it will be assumed that it operates at this version and certain features
* may be unavailable.
*/
private GuacamoleProtocolVersion protocol =
GuacamoleProtocolVersion.VERSION_1_0_0;
/**
* Waits for the instruction having the given opcode, returning that
* instruction once it has been read. If the instruction is never read,
@@ -143,6 +151,13 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
// Retrieve argument name
String arg_name = arg_names.get(i);
// Check for protocol version as first argument
if (i == 0 && arg_name.startsWith("VERSION_")) {
protocol = GuacamoleProtocolVersion.getVersion(arg_name);
arg_values[i] = protocol.toString();
continue;
}
// Get defined value for name
String value = config.getParameter(arg_name);
@@ -185,6 +200,19 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
info.getImageMimetypes().toArray(new String[0])
));
// Check for support for timezone handshake
if (protocol.isSupported(GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE)) {
// Send client timezone, if available
String timezone = info.getTimezone();
if (timezone != null) {
writer.writeInstruction(
new GuacamoleInstruction(
"timezone",
info.getTimezone()
));
}
}
// Send args
writer.writeInstruction(new GuacamoleInstruction("connect", arg_values));

View File

@@ -59,6 +59,11 @@ public class GuacamoleClientInformation {
*/
private final List<String> imageMimetypes = new ArrayList<String>();
/**
* The timezone reported by the client.
*/
private String timezone;
/**
* Returns the optimal screen width requested by the client, in pixels.
* @return The optimal screen width requested by the client, in pixels.
@@ -145,4 +150,30 @@ public class GuacamoleClientInformation {
return imageMimetypes;
}
/**
* Return the timezone as reported by the client, or null if the timezone
* is not set. Valid timezones are specified in IANA zone key format,
* also known as Olson time zone database or TZ Database.
*
* @return
* A string value of the timezone reported by the client.
*/
public String getTimezone() {
return timezone;
}
/**
* Set the string value of the timezone, or null if the timezone will not
* be provided by the client. Valid timezones are specified in IANA zone
* key format (aka Olson time zone database or tz database).
*
* @param timezone
* The string value of the timezone reported by the client, in tz
* database format, or null if the timezone is not provided by the
* client.
*/
public void setTimezone(String timezone) {
this.timezone = timezone;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.
*/
package org.apache.guacamole.protocol;
/**
* An enum that specifies protocol capabilities that can be used to help
* detect whether or not a particular protocol version contains a capability.
*/
public enum GuacamoleProtocolCapability {
/**
* Whether or not the protocol supports arbitrary ordering of the
* handshake instructions. This was introduced in VERSION_1_1_0.
*/
ARBITRARY_HANDSHAKE_ORDER(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Whether or not the protocol supports the ability to dynamically
* detect the version client and server are running in order to allow
* compatibility between differing client and server versions. This
* was introduced in VERSION_1_1_0.
*/
PROTOCOL_VERSION_DETECTION(GuacamoleProtocolVersion.VERSION_1_1_0),
/**
* Whether or not the protocol supports the timezone instruction during
* the Client-Server handshake phase. This was introduced in
* VERSION_1_1_0.
*/
TIMEZONE_HANDSHAKE(GuacamoleProtocolVersion.VERSION_1_1_0);
/**
* The minimum protocol version required to support this capability.
*/
private final GuacamoleProtocolVersion version;
/**
* Create a new enum value with the given protocol version as the minimum
* required to support the capability.
*
* @param version
* The minimum required protocol version for supporting the
* capability.
*/
GuacamoleProtocolCapability(GuacamoleProtocolVersion version) {
this.version = version;
}
/**
* Returns the minimum protocol version required to support this
* capability.
*
* @return
* The minimum protocol version required to support this capability.
*/
public GuacamoleProtocolVersion getVersion() {
return version;
}
}

View File

@@ -0,0 +1,250 @@
/*
* 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.
*/
package org.apache.guacamole.protocol;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An enum that defines the available Guacamole protocol versions that can be
* used between guacd and clients, and provides convenience methods for parsing
* and comparing versions.
*/
public enum GuacamoleProtocolVersion {
/**
* Protocol version 1.0.0 and older. Any client that doesn't explicitly
* set the protocol version will negotiate down to this protocol version.
* This requires that handshake instructions be ordered correctly, and
* lacks support for certain protocol-related features introduced in later
* versions.
*/
VERSION_1_0_0(1, 0, 0),
/**
* Protocol version 1.1.0, which introduces Client-Server version
* detection, arbitrary handshake instruction order, and support
* for passing the client timezone to the server during the handshake.
*/
VERSION_1_1_0(1, 1, 0);
/**
* A regular expression that matches the VERSION_X_Y_Z pattern, where
* X is the major version component, Y is the minor version component,
* and Z is the patch version component. This expression puts each of
* the version components in their own group so that they can be easily
* used later.
*/
private static final Pattern VERSION_PATTERN =
Pattern.compile("^VERSION_([0-9]+)_([0-9]+)_([0-9]+)$");
/**
* The major version component of the protocol version.
*/
private final int major;
/**
* The minor version component of the protocol version.
*/
private final int minor;
/**
* The patch version component of the protocol version.
*/
private final int patch;
/**
* Generate a new GuacamoleProtocolVersion object with the given
* major version, minor version, and patch version.
*
* @param major
* The integer representation of the major version component.
*
* @param minor
* The integer representation of the minor version component.
*
* @param patch
* The integer representation of the patch version component.
*/
GuacamoleProtocolVersion(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
/**
* Return the major version component of the protocol version.
*
* @return
* The integer major version component.
*/
public int getMajor() {
return major;
}
/**
* Return the minor version component of the protocol version.
*
* @return
* The integer minor version component.
*/
public int getMinor() {
return minor;
}
/**
* Return the patch version component of the protocol version.
*
* @return
* The integer patch version component.
*/
public int getPatch() {
return patch;
}
/**
* Determines whether or not this object is greater than or equal to the
* the version passed in to the method. Returns a boolean true if the
* version is the same as or greater than the other version, otherwise
* false.
*
* @param otherVersion
* The version to which this object should be compared.
*
* @return
* True if this object is greater than or equal to the other version.
*/
private boolean atLeast(GuacamoleProtocolVersion otherVersion) {
// If major is not the same, return inequality
if (major != otherVersion.getMajor())
return this.major > major;
// Major is the same, but minor is not, return minor inequality
if (minor != otherVersion.getMinor())
return this.minor > minor;
// Major and minor are equal, so return patch inequality
return patch >= otherVersion.getPatch();
}
/**
* Compare this version with the major, minor, and patch components
* provided to the method, and determine if this version is compatible
* with the provided version, returning a boolean true if it is compatible,
* otherwise false. This version is compatible with the version specified
* by the provided components if the major, minor, and patch components
* are equivalent or less than those provided.
*
* @param major
* The major version component to compare for compatibility.
*
* @param minor
* The minor version component to compare for compatibility.
*
* @param patch
* The patch version component to compare for compatibility.
*
* @return
* True if this version is compatibility with the version components
* provided, otherwise false.
*/
private boolean isCompatible(int major, int minor, int patch) {
if (this.major != major)
return this.major < major;
if (this.minor != minor)
return this.minor < minor;
return this.patch <= patch;
}
/**
* Parse the String format of the version provided and return the
* the enum value matching that version. If no value is provided, return
* null.
*
* @param version
* The String format of the version to parse.
*
* @return
* The enum value that matches the specified version, VERSION_1_0_0
* if no match is found, or null if no comparison version is provided.
*/
public static GuacamoleProtocolVersion getVersion(String version) {
// If nothing is passed in, return null
if (version == null || version.isEmpty())
return null;
// Check the string against the pattern matcher
Matcher versionMatcher = VERSION_PATTERN.matcher(version);
// If there is no RegEx match, return null
if (!versionMatcher.matches())
return null;
try {
// Try the valueOf function
return valueOf(version);
}
// If nothing matches, find the closest compatible version.
catch (IllegalArgumentException e) {
int myMajor = Integer.parseInt(versionMatcher.group(1));
int myMinor = Integer.parseInt(versionMatcher.group(2));
int myPatch = Integer.parseInt(versionMatcher.group(3));
GuacamoleProtocolVersion myVersion = VERSION_1_0_0;
// Loop through possible versions, grabbing the latest compatible
for (GuacamoleProtocolVersion v : values()) {
if (v.isCompatible(myMajor, myMinor, myPatch))
myVersion = v;
}
return myVersion;
}
}
/**
* Returns true if the specified capability is supported in the current
* protocol version, otherwise false.
*
* @param capability
* The protocol capability that is being checked for support.
*
* @return
* True if the capability is supported in the current version,
* otherwise false.
*/
public boolean isSupported(GuacamoleProtocolCapability capability) {
return atLeast(capability.getVersion());
}
}

View File

@@ -199,6 +199,10 @@
{
"name" : "static-channels",
"type" : "TEXT"
},
{
"name" : "timezone",
"type" : "TIMEZONE"
}
]
},

View File

@@ -100,6 +100,10 @@
"name" : "terminal-type",
"type" : "ENUM",
"options" : [ "", "xterm", "xterm-256color", "vt220", "vt100", "ansi", "linux" ]
},
{
"name" : "timezone",
"type" : "TIMEZONE"
}
]
},

View File

@@ -494,6 +494,13 @@
</dependency>
<!-- JSTZ for TimeZone Detection -->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>jstz</artifactId>
<version>1.0.10</version>
</dependency>
</dependencies>
</project>

View File

@@ -605,6 +605,36 @@ licenses; we recommend you read them, as their terms may differ from the
terms above.
JSTZ (https://pellepim.bitbucket.io/jstz/)
------------------------------------------
Version: 1.0.10
From: 'Jon Nylander' (https://pellepim.bitbucket.io/jstz/)
License(s):
MIT (bundled/jstz-1.0.10/LICENSE)
Copyright (c) 2012 Jon Nylander, project maintained at
https://bitbucket.org/pellepim/jstimezonedetect
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to
do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Logback (http://logback.qos.ch/)
--------------------------------

View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2012 Jon Nylander, project maintained at
https://bitbucket.org/pellepim/jstimezonedetect
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to
do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -96,6 +96,11 @@ public abstract class TunnelRequest {
*/
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
/**
* The name of the parameter specifying the timezone of the client.
*/
public static final String TIMEZONE_PARAMETER = "GUAC_TIMEZONE";
/**
* All supported object types that can be used as the destination of a
* tunnel.
@@ -366,4 +371,15 @@ public abstract class TunnelRequest {
return getParameterValues(IMAGE_PARAMETER);
}
/**
* Returns the tz database value of the timezone declared by the client
* within the tunnel request.
*
* @return
* The tz database value of the timezone parameter as reported by
* the client.
*/
public String getTimezone() {
return getParameter(TIMEZONE_PARAMETER);
}
}

View File

@@ -167,6 +167,11 @@ public class TunnelRequestService {
if (imageMimetypes != null)
info.getImageMimetypes().addAll(imageMimetypes);
// Get the timezone value
String timezone = request.getTimezone();
if (timezone != null & !timezone.isEmpty())
info.setTimezone(timezone);
return info;
}

View File

@@ -42,6 +42,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
var authenticationService = $injector.get('authenticationService');
var connectionGroupService = $injector.get('connectionGroupService');
var connectionService = $injector.get('connectionService');
var preferenceService = $injector.get('preferenceService');
var requestService = $injector.get('requestService');
var tunnelService = $injector.get('tunnelService');
var guacAudio = $injector.get('guacAudio');
@@ -225,6 +226,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
+ "&GUAC_WIDTH=" + Math.floor(optimal_width)
+ "&GUAC_HEIGHT=" + Math.floor(optimal_height)
+ "&GUAC_DPI=" + Math.floor(optimal_dpi)
+ "&GUAC_TIMEZONE=" + encodeURIComponent(preferenceService.preferences.timezone)
+ (connectionParameters ? '&' + connectionParameters : '');
// Add audio mimetypes to connect string

View File

@@ -99,6 +99,18 @@ angular.module('settings').provider('preferenceService', ['$injector',
};
/**
* Return the timezone detected for the current browser session
* by the JSTZ timezone library.
*
* @returns String
* The name of the currently-detected timezone in IANA zone key
* format (Olson time zone database).
*/
var getDetectedTimezone = function getDetectedTimezone() {
return jstz.determine().name();
};
/**
* All currently-set preferences, as name/value pairs. Each property name
* corresponds to the name of a preference.
@@ -128,7 +140,15 @@ angular.module('settings').provider('preferenceService', ['$injector',
*
* @type String
*/
language : getDefaultLanguageKey()
language : getDefaultLanguageKey(),
/**
* The timezone set by the user, in IANA zone key format (Olson time
* zone database).
*
* @type String
*/
timezone : getDetectedTimezone()
};

View File

@@ -18,7 +18,7 @@
*/
.preferences .update-password .form,
.preferences .language .form {
.preferences .locale .form {
padding-left: 0.5em;
border-left: 3px solid rgba(0, 0, 0, 0.125);
}

View File

@@ -1,8 +1,8 @@
<div class="preferences" ng-class="{loading: !isLoaded()}">
<!-- Language settings -->
<div class="settings section language">
<p>{{'SETTINGS_PREFERENCES.HELP_LANGUAGE' | translate}}</p>
<!-- Locale settings -->
<div class="settings section locale">
<p>{{'SETTINGS_PREFERENCES.HELP_LOCALE' | translate}}</p>
<!-- Language selection -->
<div class="form">
@@ -13,6 +13,15 @@
</tr>
</table>
</div>
<!-- Timezone selection -->
<div class="form">
<guac-form-field
field="{ 'type' : 'TIMEZONE', 'name' : 'timezone' }"
model="preferences.timezone"
namespace="'SETTINGS_PREFERENCES'">
</guac-form-field>
</div>
</div>
<!-- Password update -->

View File

@@ -85,6 +85,9 @@
<script type="text/javascript" src="webjars/angular-translate-interpolation-messageformat/2.16.0/angular-translate-interpolation-messageformat.min.js"></script>
<script type="text/javascript" src="webjars/angular-translate-loader-static-files/2.16.0/angular-translate-loader-static-files.min.js"></script>
<!-- JSTZ -->
<script type="text/javascript" src="webjars/jstz/1.0.10/dist/jstz.min.js"></script>
<!-- Polyfills for the "datalist" element, Blob and the FileSaver API -->
<script type="text/javascript" src="webjars/blob-polyfill/1.0.20150320/Blob.js"></script>
<script type="text/javascript" src="webjars/datalist-polyfill/1.14.0/datalist-polyfill.min.js"></script>

View File

@@ -415,6 +415,7 @@
"FIELD_HEADER_REMOTE_APP_ARGS" : "Parameters:",
"FIELD_HEADER_REMOTE_APP_DIR" : "Working directory:",
"FIELD_HEADER_REMOTE_APP" : "Program:",
"FIELD_HEADER_TIMEZONE" : "Timezone:",
"FIELD_HEADER_SECURITY" : "Security mode:",
"FIELD_HEADER_SERVER_LAYOUT" : "Keyboard layout:",
"FIELD_HEADER_SFTP_DIRECTORY" : "Default upload directory:",
@@ -757,6 +758,7 @@
"FIELD_HEADER_PASSWORD_OLD" : "Current Password:",
"FIELD_HEADER_PASSWORD_NEW" : "New Password:",
"FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Confirm New Password:",
"FIELD_HEADER_TIMEZONE" : "Timezone:",
"FIELD_HEADER_USERNAME" : "Username:",
"HELP_DEFAULT_INPUT_METHOD" : "The default input method determines how keyboard events are received by Guacamole. Changing this setting may be necessary when using a mobile device, or when typing through an IME. This setting can be overridden on a per-connection basis within the Guacamole menu.",
@@ -764,7 +766,7 @@
"HELP_INPUT_METHOD_NONE" : "@:CLIENT.HELP_INPUT_METHOD_NONE",
"HELP_INPUT_METHOD_OSK" : "@:CLIENT.HELP_INPUT_METHOD_OSK",
"HELP_INPUT_METHOD_TEXT" : "@:CLIENT.HELP_INPUT_METHOD_TEXT",
"HELP_LANGUAGE" : "Select a different language below to change the language of all text within Guacamole. Available choices will depend on which languages are installed.",
"HELP_LOCALE" : "Options below are related to the locale of the user and will impact how various parts of the interface are displayed.",
"HELP_MOUSE_MODE_ABSOLUTE" : "@:CLIENT.HELP_MOUSE_MODE_ABSOLUTE",
"HELP_MOUSE_MODE_RELATIVE" : "@:CLIENT.HELP_MOUSE_MODE_RELATIVE",
"HELP_UPDATE_PASSWORD" : "If you wish to change your password, enter your current password and the desired new password below, and click \"Update Password\". The change will take effect immediately.",