Merge 1.3.0 changes back to master.

This commit is contained in:
Virtually Nick
2020-11-25 18:55:26 -05:00
12 changed files with 297 additions and 165 deletions

View File

@@ -201,87 +201,52 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
ModeledConnectionGroup connectionGroup);
/**
* Returns a guacamole configuration containing the protocol and parameters
* from the given connection. If the ID of an active connection is
* provided, that connection will be joined instead of starting a new
* primary connection. If tokens are used in the connection parameter
* values, credentials from the given user will be substituted
* appropriately.
*
* @param user
* The user whose credentials should be used if necessary.
* Returns a GuacamoleConfiguration which connects to the given connection.
* If the ID of an active connection is provided, that active connection
* will be joined rather than establishing an entirely new connection. If
* a sharing profile is provided, the parameters associated with that
* sharing profile will be used to define the access provided to the user
* accessing the shared connection.
*
* @param connection
* The connection whose protocol and parameters should be added to the
* returned configuration.
* The connection that the user is connecting to.
*
* @param connectionID
* The ID of the active connection to be joined, as returned by guacd,
* or null if a new primary connection should be established.
*
* @return
* A GuacamoleConfiguration containing the protocol and parameters from
* the given connection.
*/
private GuacamoleConfiguration getGuacamoleConfiguration(RemoteAuthenticatedUser user,
ModeledConnection connection, String connectionID) {
// Generate configuration from available data
GuacamoleConfiguration config = new GuacamoleConfiguration();
// Join existing active connection, if any
if (connectionID != null)
config.setConnectionID(connectionID);
// Set protocol from connection if not joining an active connection
else {
ConnectionModel model = connection.getModel();
config.setProtocol(model.getProtocol());
}
// Set parameters from associated data
Collection<ConnectionParameterModel> parameters = connectionParameterMapper.select(connection.getIdentifier());
for (ConnectionParameterModel parameter : parameters)
config.setParameter(parameter.getName(), parameter.getValue());
return config;
}
/**
* Returns a guacamole configuration which joins the active connection
* having the given ID, using the provided sharing profile to restrict the
* access provided to the user accessing the shared connection. If tokens
* are used in the connection parameter values of the sharing profile,
* credentials from the given user will be substituted appropriately.
*
* @param user
* The user whose credentials should be used if necessary.
* The ID of the active connection being joined, as provided by guacd
* when the original connection was established, or null if a new
* connection should be established instead.
*
* @param sharingProfile
* The sharing profile whose associated parameters dictate the level
* of access granted to the user joining the connection.
*
* @param connectionID
* The ID of the connection being joined, as provided by guacd when the
* original connection was established, or null if a new connection
* should be created instead.
* of access granted to the user joining the connection, or null if the
* parameters associated with the connection should be used.
*
* @return
* A GuacamoleConfiguration containing the protocol and parameters from
* the given connection.
* A GuacamoleConfiguration defining the requested, possibly shared
* connection.
*/
private GuacamoleConfiguration getGuacamoleConfiguration(RemoteAuthenticatedUser user,
ModeledSharingProfile sharingProfile, String connectionID) {
private GuacamoleConfiguration getGuacamoleConfiguration(
ModeledConnection connection, String connectionID,
ModeledSharingProfile sharingProfile) {
ConnectionModel model = connection.getModel();
// Generate configuration from available data
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol(model.getProtocol());
config.setConnectionID(connectionID);
// Set parameters from associated data
Collection<SharingProfileParameterModel> parameters = sharingProfileParameterMapper.select(sharingProfile.getIdentifier());
for (SharingProfileParameterModel parameter : parameters)
config.setParameter(parameter.getName(), parameter.getValue());
if (sharingProfile != null) {
Collection<SharingProfileParameterModel> parameters = sharingProfileParameterMapper.select(sharingProfile.getIdentifier());
for (SharingProfileParameterModel parameter : parameters)
config.setParameter(parameter.getName(), parameter.getValue());
}
else {
Collection<ConnectionParameterModel> parameters = connectionParameterMapper.select(connection.getIdentifier());
for (ConnectionParameterModel parameter : parameters)
config.setParameter(parameter.getName(), parameter.getValue());
}
return config;
@@ -488,7 +453,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
if (activeConnection.isPrimaryConnection()) {
activeConnections.put(connection.getIdentifier(), activeConnection);
activeConnectionGroups.put(connection.getParentIdentifier(), activeConnection);
config = getGuacamoleConfiguration(activeConnection.getUser(), connection, activeConnection.getConnectionID());
config = getGuacamoleConfiguration(connection, activeConnection.getConnectionID(), null);
}
// If we ARE joining an active connection under the restrictions of
@@ -502,8 +467,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS
// Build configuration from the sharing profile and the ID of
// the connection being joined
config = getGuacamoleConfiguration(activeConnection.getUser(),
activeConnection.getSharingProfile(), connectionID);
config = getGuacamoleConfiguration(connection, connectionID, activeConnection.getSharingProfile());
}

View File

@@ -73,6 +73,20 @@ Guacamole.Tunnel = function() {
};
/**
* Changes the stored UUID that uniquely identifies this tunnel, firing the
* onuuid event if a handler has been defined.
*
* @private
* @param {String} uuid
* The new state of this tunnel.
*/
this.setUUID = function setUUID(uuid) {
this.uuid = uuid;
if (this.onuuid)
this.onuuid(uuid);
};
/**
* Returns whether this tunnel is currently connected.
*
@@ -119,6 +133,15 @@ Guacamole.Tunnel = function() {
*/
this.uuid = null;
/**
* Fired when the UUID that uniquely identifies this tunnel is known.
*
* @event
* @param {String}
* The UUID uniquely identifying this tunnel.
*/
this.onuuid = null;
/**
* Fired whenever an error is encountered by the tunnel.
*
@@ -706,7 +729,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
reset_timeout();
// Get UUID from response
tunnel.uuid = connect_xmlhttprequest.responseText;
tunnel.setUUID(connect_xmlhttprequest.responseText);
// Mark as open
tunnel.setState(Guacamole.Tunnel.State.OPEN);
@@ -1019,7 +1042,7 @@ Guacamole.WebSocketTunnel = function(tunnelURL) {
// Associate tunnel UUID if received
if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE)
tunnel.uuid = elements[0];
tunnel.setUUID(elements[0]);
// Tunnel is now open and UUID is available
tunnel.setState(Guacamole.Tunnel.State.OPEN);
@@ -1155,11 +1178,18 @@ Guacamole.ChainedTunnel = function(tunnelChain) {
* @private
*/
function commit_tunnel() {
tunnel.onstatechange = chained_tunnel.onstatechange;
tunnel.oninstruction = chained_tunnel.oninstruction;
tunnel.onerror = chained_tunnel.onerror;
chained_tunnel.uuid = tunnel.uuid;
tunnel.onuuid = chained_tunnel.onuuid;
// Assign UUID if already known
if (tunnel.uuid)
chained_tunnel.setUUID(tunnel.uuid);
committedTunnel = tunnel;
}
// Wrap own onstatechange within current tunnel

View File

@@ -57,14 +57,14 @@
<build>
<plugins>
<!-- Written for 1.6 -->
<!-- Written for 1.8 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-Xlint:all</arg>
<arg>-Werror</arg>

View File

@@ -0,0 +1,84 @@
/*
* 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.net;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
/**
* GuacamoleSocket implementation which simply delegates all function calls to
* an underlying GuacamoleSocket.
*/
public class DelegatingGuacamoleSocket implements GuacamoleSocket {
/**
* The wrapped socket.
*/
private final GuacamoleSocket socket;
/**
* Wraps the given GuacamoleSocket such that all function calls against
* this DelegatingGuacamoleSocket will be delegated to it.
*
* @param socket
* The GuacamoleSocket to wrap.
*/
public DelegatingGuacamoleSocket(GuacamoleSocket socket) {
this.socket = socket;
}
/**
* Returns the underlying GuacamoleSocket wrapped by this
* DelegatingGuacamoleSocket.
*
* @return
* The GuacamoleSocket wrapped by this DelegatingGuacamoleSocket.
*/
protected GuacamoleSocket getDelegateSocket() {
return socket;
}
@Override
public String getProtocol() {
return socket.getProtocol();
}
@Override
public GuacamoleReader getReader() {
return socket.getReader();
}
@Override
public GuacamoleWriter getWriter() {
return socket.getWriter();
}
@Override
public void close() throws GuacamoleException {
socket.close();
}
@Override
public boolean isOpen() {
return socket.isOpen();
}
}

View File

@@ -29,6 +29,24 @@ import org.apache.guacamole.io.GuacamoleWriter;
*/
public interface GuacamoleSocket {
/**
* Returns the name of the protocol to be used. If the protocol is not
* known or the implementation refuses to reveal the underlying protocol,
* null is returned.
*
* <p>Implementations <strong>should</strong> aim to expose the name of the
* underlying protocol, such that protocol-specific responses like the
* "required" and "argv" instructions can be handled correctly by code
* consuming the GuacamoleSocket.
*
* @return
* The name of the protocol to be used, or null if this information is
* not available.
*/
public default String getProtocol() {
return null;
}
/**
* Returns a GuacamoleReader which can be used to read from the
* Guacamole instruction stream associated with the connection

View File

@@ -24,6 +24,7 @@ import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
/**
@@ -36,12 +37,7 @@ import org.apache.guacamole.net.GuacamoleSocket;
* this GuacamoleSocket from manually controlling the initial protocol
* handshake.
*/
public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
/**
* The wrapped socket.
*/
private GuacamoleSocket socket;
public class ConfiguredGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* The configuration to use when performing the Guacamole protocol
@@ -125,7 +121,7 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
GuacamoleConfiguration config,
GuacamoleClientInformation info) throws GuacamoleException {
this.socket = socket;
super(socket);
this.config = config;
// Get reader and writer
@@ -268,23 +264,8 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket {
}
@Override
public GuacamoleWriter getWriter() {
return socket.getWriter();
}
@Override
public GuacamoleReader getReader() {
return socket.getReader();
}
@Override
public void close() throws GuacamoleException {
socket.close();
}
@Override
public boolean isOpen() {
return socket.isOpen();
public String getProtocol() {
return getConfiguration().getProtocol();
}
}

View File

@@ -28,7 +28,7 @@ import org.apache.guacamole.GuacamoleUpstreamNotFoundException;
import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
import org.apache.guacamole.GuacamoleUpstreamUnavailableException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
* constructor, allowing a different socket to be substituted prior to
* fulfilling the connection.
*/
public class FailoverGuacamoleSocket implements GuacamoleSocket {
public class FailoverGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* Logger for this class.
@@ -54,11 +54,6 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
*/
private static final int DEFAULT_INSTRUCTION_QUEUE_LIMIT = 131072;
/**
* The wrapped socket being used.
*/
private final GuacamoleSocket socket;
/**
* Queue of all instructions read while this FailoverGuacamoleSocket was
* being constructed.
@@ -158,6 +153,8 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
final int instructionQueueLimit)
throws GuacamoleException, GuacamoleUpstreamException {
super(socket);
int totalQueueSize = 0;
GuacamoleInstruction instruction;
@@ -189,8 +186,6 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
}
this.socket = socket;
}
/**
@@ -230,7 +225,7 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
@Override
public boolean available() throws GuacamoleException {
return !instructionQueue.isEmpty() || socket.getReader().available();
return !instructionQueue.isEmpty() || getDelegateSocket().getReader().available();
}
@Override
@@ -244,7 +239,7 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
return instruction.toString().toCharArray();
}
return socket.getReader().read();
return getDelegateSocket().getReader().read();
}
@@ -258,7 +253,7 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
if (!instructionQueue.isEmpty())
return instructionQueue.remove();
return socket.getReader().readInstruction();
return getDelegateSocket().getReader().readInstruction();
}
@@ -269,19 +264,4 @@ public class FailoverGuacamoleSocket implements GuacamoleSocket {
return queuedReader;
}
@Override
public GuacamoleWriter getWriter() {
return socket.getWriter();
}
@Override
public void close() throws GuacamoleException {
socket.close();
}
@Override
public boolean isOpen() {
return socket.isOpen();
}
}

View File

@@ -19,21 +19,16 @@
package org.apache.guacamole.protocol;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.DelegatingGuacamoleSocket;
import org.apache.guacamole.net.GuacamoleSocket;
/**
* Implementation of GuacamoleSocket which allows individual instructions to be
* intercepted, overridden, etc.
*/
public class FilteredGuacamoleSocket implements GuacamoleSocket {
/**
* Wrapped GuacamoleSocket.
*/
private final GuacamoleSocket socket;
public class FilteredGuacamoleSocket extends DelegatingGuacamoleSocket {
/**
* A reader for the wrapped GuacamoleSocket which may be filtered.
@@ -58,7 +53,8 @@ public class FilteredGuacamoleSocket implements GuacamoleSocket {
* instructions, if any.
*/
public FilteredGuacamoleSocket(GuacamoleSocket socket, GuacamoleFilter readFilter, GuacamoleFilter writeFilter) {
this.socket = socket;
super(socket);
// Apply filter to reader
if (readFilter != null)
@@ -84,14 +80,4 @@ public class FilteredGuacamoleSocket implements GuacamoleSocket {
return writer;
}
@Override
public void close() throws GuacamoleException {
socket.close();
}
@Override
public boolean isOpen() {
return socket.isOpen();
}
}

View File

@@ -92,8 +92,7 @@ public class GuacamoleConfiguration implements Serializable {
/**
* Sets the ID of the connection being joined, if any. If no connection
* is being joined, this value must be omitted, and the protocol must be
* set instead.
* is being joined, this value must be omitted.
*
* @param connectionID The ID of the connection being joined.
*/
@@ -103,15 +102,34 @@ public class GuacamoleConfiguration implements Serializable {
/**
* Returns the name of the protocol to be used.
* @return The name of the protocol to be used.
*
* @return
* The name of the protocol to be used.
*/
public String getProtocol() {
return protocol;
}
/**
* Sets the name of the protocol to be used.
* @param protocol The name of the protocol to be used.
* Sets the name of the protocol to be used. If no connection is being
* joined (a new connection is being established), this value must be set.
*
* <p>If a connection is being joined, <strong>this value should still be
* set</strong> to ensure that protocol-specific responses like the
* "required" and "argv" instructions can be understood in their proper
* context by other code that may consume this GuacamoleConfiguration like
* {@link ConfiguredGuacamoleSocket}.
*
* <p>If this value is unavailable or remains unset, it is still possible
* to join an established connection using
* {@link #setConnectionID(java.lang.String)}, however protocol-specific
* responses like the "required" and "argv" instructions might not be
* possible to handle correctly if the underlying protocol is not made
* available through some other means to the client receiving those
* responses.
*
* @param protocol
* The name of the protocol to be used.
*/
public void setProtocol(String protocol) {
this.protocol = protocol;

View File

@@ -24,6 +24,7 @@ import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -31,8 +32,10 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.protocols.ProtocolInfo;
import org.apache.guacamole.rest.activeconnection.APIActiveConnection;
import org.apache.guacamole.rest.directory.DirectoryObjectResource;
import org.apache.guacamole.rest.directory.DirectoryObjectResourceFactory;
@@ -57,6 +60,12 @@ public class TunnelResource {
*/
private final UserTunnel tunnel;
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* A factory which can be used to create instances of resources representing
* ActiveConnections.
@@ -106,6 +115,39 @@ public class TunnelResource {
}
/**
* Retrieves the underlying protocol used by the connection associated with
* this tunnel. If possible, the parameters available for that protocol are
* retrieved, as well.
*
* @return
* A ProtocolInfo object describing the protocol used by the connection
* associated with this tunnel.
*
* @throws GuacamoleException
* If the protocol used by the connection associated with this tunnel
* cannot be determined.
*/
@GET
@Path("protocol")
public ProtocolInfo getProtocol() throws GuacamoleException {
// Pull protocol name from underlying socket
String protocol = tunnel.getSocket().getProtocol();
if (protocol == null)
throw new GuacamoleResourceNotFoundException("Protocol of tunnel is not known/exposed.");
// If there is no such protocol defined, provide as much info as is
// known (just the name)
ProtocolInfo info = environment.getProtocol(protocol);
if (info == null)
return new ProtocolInfo(protocol);
// All protocol information for this tunnel is known
return info;
}
/**
* Intercepts and returns the entire contents of a specific stream.
*

View File

@@ -386,7 +386,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
status.code);
});
};
// Pull protocol-specific information from tunnel once tunnel UUID is
// known
tunnel.onuuid = function tunnelAssignedUUID(uuid) {
tunnelService.getProtocol(uuid).then(function protocolRetrieved(protocol) {
managedClient.protocol = protocol.name;
managedClient.forms = protocol.connectionForms;
}, requestService.WARN);
};
// Update connection state as tunnel state changes
tunnel.onstatechange = function tunnelStateChanged(state) {
$rootScope.$evalAsync(function updateTunnelState() {
@@ -612,14 +621,9 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
// If using a connection, pull connection name and protocol information
if (clientIdentifier.type === ClientIdentifier.Types.CONNECTION) {
$q.all({
connection : connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id),
protocols : schemaService.getProtocols(clientIdentifier.dataSource)
})
.then(function dataRetrieved(values) {
managedClient.name = managedClient.title = values.connection.name;
managedClient.protocol = values.connection.protocol;
managedClient.forms = values.protocols[values.connection.protocol].connectionForms;
connectionService.getConnection(clientIdentifier.dataSource, clientIdentifier.id)
.then(function connectionRetrieved(connection) {
managedClient.name = managedClient.title = connection.name;
}, requestService.WARN);
}
@@ -640,14 +644,9 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
// Attempt to retrieve connection details only if the
// underlying connection is known
if (activeConnection.connectionIdentifier) {
$q.all({
connection : connectionService.getConnection(clientIdentifier.dataSource, activeConnection.connectionIdentifier),
protocols : schemaService.getProtocols(clientIdentifier.dataSource)
})
.then(function dataRetrieved(values) {
managedClient.name = managedClient.title = values.connection.name;
managedClient.protocol = values.connection.protocol;
managedClient.forms = values.protocols[values.connection.protocol].connectionForms;
connectionService.getConnection(clientIdentifier.dataSource, activeConnection.connectionIdentifier)
.then(function connectionRetrieved(connection) {
managedClient.name = managedClient.title = connection.name;
}, requestService.WARN);
}

View File

@@ -79,6 +79,36 @@ angular.module('rest').factory('tunnelService', ['$injector',
};
/**
* Makes a request to the REST API to retrieve the underlying protocol of
* the connection associated with a particular tunnel, returning a promise
* that provides a @link{Protocol} object if successful.
*
* @param {String} tunnel
* The UUID of the tunnel associated with the Guacamole connection
* whose underlying protocol is being retrieved.
*
* @returns {Promise.<Protocol>}
* A promise which will resolve with a @link{Protocol} object upon
* success.
*/
service.getProtocol = function getProtocol(tunnel) {
// Build HTTP parameters set
var httpParameters = {
token : authenticationService.getCurrentToken()
};
// Retrieve the protocol details of the specified tunnel
return requestService({
method : 'GET',
url : 'api/session/tunnels/' + encodeURIComponent(tunnel)
+ '/protocol',
params : httpParameters
});
};
/**
* Retrieves the set of sharing profiles that the current user can use to
* share the active connection of the given tunnel.