GUAC-442: Tie new WebSocket implementation into authentication layer. Generalize tunnel requests.

This commit is contained in:
Michael Jumper
2014-10-09 21:01:34 -07:00
parent 99f59a6a4c
commit c30afba91d
8 changed files with 388 additions and 129 deletions

View File

@@ -42,66 +42,9 @@ public class BasicGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
*/
private static final Logger logger = LoggerFactory.getLogger(BasicGuacamoleTunnelServlet.class);
/**
* All supported identifier types.
*/
public static enum IdentifierType {
/**
* The unique identifier of a connection.
*/
CONNECTION("c/"),
/**
* The unique identifier of a connection group.
*/
CONNECTION_GROUP("g/");
/**
* The prefix which precedes an identifier of this type.
*/
final String PREFIX;
/**
* Defines an IdentifierType having the given prefix.
* @param prefix The prefix which will precede any identifier of this
* type, thus differentiating it from other identifier
* types.
*/
IdentifierType(String prefix) {
PREFIX = prefix;
}
/**
* Given an identifier, determines the corresponding identifier type.
*
* @param identifier The identifier whose type should be identified.
* @return The identified identifier type.
*/
static IdentifierType getType(String identifier) {
// If null, no known identifier
if (identifier == null)
return null;
// Connection identifiers
if (identifier.startsWith(CONNECTION.PREFIX))
return CONNECTION;
// Connection group identifiers
if (identifier.startsWith(CONNECTION_GROUP.PREFIX))
return CONNECTION_GROUP;
// Otherwise, unknown
return null;
}
};
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
return BasicTunnelRequestUtility.createTunnel(request);
return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request));
}
}

View File

@@ -22,9 +22,8 @@
package org.glyptodon.guacamole.net.basic;
import java.util.Arrays;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
@@ -63,63 +62,6 @@ public class BasicTunnelRequestUtility {
*/
private static final Logger logger = LoggerFactory.getLogger(BasicTunnelRequestUtility.class);
/**
* All supported identifier types.
*/
private static enum IdentifierType {
/**
* The unique identifier of a connection.
*/
CONNECTION("c/"),
/**
* The unique identifier of a connection group.
*/
CONNECTION_GROUP("g/");
/**
* The prefix which precedes an identifier of this type.
*/
final String PREFIX;
/**
* Defines an IdentifierType having the given prefix.
* @param prefix The prefix which will precede any identifier of this
* type, thus differentiating it from other identifier
* types.
*/
IdentifierType(String prefix) {
PREFIX = prefix;
}
/**
* Given an identifier, determines the corresponding identifier type.
*
* @param identifier The identifier whose type should be identified.
* @return The identified identifier type.
*/
static IdentifierType getType(String identifier) {
// If null, no known identifier
if (identifier == null)
return null;
// Connection identifiers
if (identifier.startsWith(CONNECTION.PREFIX))
return CONNECTION;
// Connection group identifiers
if (identifier.startsWith(CONNECTION_GROUP.PREFIX))
return CONNECTION_GROUP;
// Otherwise, unknown
return null;
}
};
/**
* Notifies all listeners in the given collection that a tunnel has been
* connected.
@@ -200,18 +142,21 @@ public class BasicTunnelRequestUtility {
}
/**
* Creates a new tunnel using the parameters and credentials present in
* the given request.
*
* @param request The HttpServletRequest describing the tunnel to create.
* @param request The request describing the tunnel to create.
* @return The created tunnel, or null if the tunnel could not be created.
* @throws GuacamoleException If an error occurs while creating the tunnel.
*/
public static GuacamoleTunnel createTunnel(HttpServletRequest request)
public static GuacamoleTunnel createTunnel(TunnelRequest request)
throws GuacamoleException {
HttpSession httpSession = request.getSession(true);
HttpSession httpSession = request.getSession();
if (httpSession == null)
throw new GuacamoleSecurityException("Cannot connect - user not logged in.");
// Get listeners
final SessionListenerCollection listeners;
@@ -225,7 +170,7 @@ public class BasicTunnelRequestUtility {
// Get ID of connection
String id = request.getParameter("id");
IdentifierType id_type = IdentifierType.getType(id);
TunnelRequest.IdentifierType id_type = TunnelRequest.IdentifierType.getType(id);
// Do not continue if unable to determine type
if (id_type == null)
@@ -266,14 +211,14 @@ public class BasicTunnelRequestUtility {
info.setOptimalResolution(Integer.parseInt(dpi));
// Add audio mimetypes
String[] audio_mimetypes = request.getParameterValues("audio");
List<String> audio_mimetypes = request.getParameterValues("audio");
if (audio_mimetypes != null)
info.getAudioMimetypes().addAll(Arrays.asList(audio_mimetypes));
info.getAudioMimetypes().addAll(audio_mimetypes);
// Add video mimetypes
String[] video_mimetypes = request.getParameterValues("video");
List<String> video_mimetypes = request.getParameterValues("video");
if (video_mimetypes != null)
info.getVideoMimetypes().addAll(Arrays.asList(video_mimetypes));
info.getVideoMimetypes().addAll(video_mimetypes);
// Create connected socket from identifier
GuacamoleSocket socket;
@@ -295,7 +240,7 @@ public class BasicTunnelRequestUtility {
// Connect socket
socket = connection.connect(info);
logger.info("Successful connection from {} to \"{}\".", request.getRemoteAddr(), id);
logger.info("Successful connection from to \"{}\".", id);
break;
}
@@ -315,7 +260,7 @@ public class BasicTunnelRequestUtility {
// Connect socket
socket = group.connect(info);
logger.info("Successful connection from {} to group \"{}\".", request.getRemoteAddr(), id);
logger.info("Successful connection to group \"{}\".", id);
break;
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* 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.
*/
package org.glyptodon.guacamole.net.basic;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* HTTP-specific implementation of TunnelRequest.
*
* @author Michael Jumper
*/
public class HTTPTunnelRequest implements TunnelRequest {
/**
* The wrapped HttpServletRequest.
*/
private final HttpServletRequest request;
/**
* Creates a TunnelRequest implementation which delegates parameter and
* session retrieval to the given HttpServletRequest.
*
* @param request The HttpServletRequest to wrap.
*/
public HTTPTunnelRequest(HttpServletRequest request) {
this.request = request;
}
@Override
public HttpSession getSession() {
return request.getSession();
}
@Override
public String getParameter(String name) {
return request.getParameter(name);
}
@Override
public List<String> getParameterValues(String name) {
return Arrays.asList(request.getParameterValues(name));
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* 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.
*/
package org.glyptodon.guacamole.net.basic;
import java.util.List;
import javax.servlet.http.HttpSession;
/**
* Request interface which provides only the functions absolutely required
* to retrieve and connect to a tunnel.
*
* @author Michael Jumper
*/
public interface TunnelRequest {
/**
* All supported identifier types.
*/
public static enum IdentifierType {
/**
* The unique identifier of a connection.
*/
CONNECTION("c/"),
/**
* The unique identifier of a connection group.
*/
CONNECTION_GROUP("g/");
/**
* The prefix which precedes an identifier of this type.
*/
final String PREFIX;
/**
* Defines an IdentifierType having the given prefix.
* @param prefix The prefix which will precede any identifier of this
* type, thus differentiating it from other identifier
* types.
*/
IdentifierType(String prefix) {
PREFIX = prefix;
}
/**
* Given an identifier, determines the corresponding identifier type.
*
* @param identifier The identifier whose type should be identified.
* @return The identified identifier type.
*/
static IdentifierType getType(String identifier) {
// If null, no known identifier
if (identifier == null)
return null;
// Connection identifiers
if (identifier.startsWith(CONNECTION.PREFIX))
return CONNECTION;
// Connection group identifiers
if (identifier.startsWith(CONNECTION_GROUP.PREFIX))
return CONNECTION_GROUP;
// Otherwise, unknown
return null;
}
};
/**
* Returns the current HTTP session, or null if no session yet exists.
*
* @return The current HTTP session, or null if no session yet exists.
*/
public HttpSession getSession();
/**
* Returns the value of the parameter having the given name.
*
* @param name The name of the parameter to return.
* @return The value of the parameter having the given name, or null
* if no such parameter was specified.
*/
public String getParameter(String name);
/**
* Returns a list of all values specified for the given parameter.
*
* @param name The name of the parameter to return.
* @return All values of the parameter having the given name , or null
* if no such parameter was specified.
*/
public List<String> getParameterValues(String name);
}

View File

@@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility;
import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest;
/**
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
@@ -37,7 +38,7 @@ public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunn
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException {
return BasicTunnelRequestUtility.createTunnel(request);
return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request));
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* 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.
*/
package org.glyptodon.guacamole.net.basic.websocket.jsr;
import java.util.Map;
import javax.websocket.EndpointConfig;
import javax.websocket.HandshakeResponse;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility;
/**
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than
* HTTP, properly parsing connection IDs included in the connection request.
*/
@ServerEndpoint(value = "/websocket-tunnel",
subprotocols = {"guacamole"},
configurator = BasicGuacamoleWebSocketTunnelEndpoint.Configurator.class)
public class BasicGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
/**
* Unique string which shall be used to store the GuacamoleTunnel
* associated with a WebSocket connection.
*/
private static final String TUNNEL_USER_PROPERTY = "WS_GUAC_TUNNEL";
/**
* Unique string which shall be used to store any GuacamoleException that
* occurs while retrieving the tunnel during the handshake.
*/
private static final String ERROR_USER_PROPERTY = "WS_GUAC_TUNNEL_ERROR";
/**
* Configurator implementation which stores the requested GuacamoleTunnel
* within the user properties. The GuacamoleTunnel will be later retrieved
* during the connection process.
*/
public static class Configurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
super.modifyHandshake(config, request, response);
// Attempt tunnel creation
Map<String, Object> userProperties = config.getUserProperties();
userProperties.clear();
try {
// Store new tunnel within user properties
GuacamoleTunnel tunnel = BasicTunnelRequestUtility.createTunnel(new WebSocketTunnelRequest(request));
if (tunnel != null)
userProperties.put(TUNNEL_USER_PROPERTY, tunnel);
}
catch (GuacamoleException e) {
userProperties.put(ERROR_USER_PROPERTY, e);
}
}
}
@Override
protected GuacamoleTunnel createTunnel(Session session, EndpointConfig config) throws GuacamoleException {
// Throw any error that occurred during tunnel creation
Map<String, Object> userProperties = config.getUserProperties();
GuacamoleException tunnelError = (GuacamoleException) userProperties.get(ERROR_USER_PROPERTY);
if (tunnelError != null)
throw tunnelError;
// Return created tunnel, if any
return (GuacamoleTunnel) userProperties.get(TUNNEL_USER_PROPERTY);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* 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.
*/
package org.glyptodon.guacamole.net.basic.websocket.jsr;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpSession;
import javax.websocket.server.HandshakeRequest;
import org.glyptodon.guacamole.net.basic.TunnelRequest;
/**
* WebSocket-specific implementation of TunnelRequest.
*
* @author Michael Jumper
*/
public class WebSocketTunnelRequest implements TunnelRequest {
/**
* The wrapped HandshakeRequest.
*/
private final HandshakeRequest request;
/**
* All parameters passed via HTTP to the WebSocket handshake.
*/
private final Map<String, List<String>> handshakeParameters;
/**
* Creates a TunnelRequest implementation which delegates parameter and
* session retrieval to the given HandshakeRequest.
*
* @param request The HandshakeRequest to wrap.
*/
public WebSocketTunnelRequest(HandshakeRequest request) {
this.request = request;
this.handshakeParameters = request.getParameterMap();
}
@Override
public HttpSession getSession() {
return (HttpSession) request.getHttpSession();
}
@Override
public String getParameter(String name) {
// Pull list of values, if present
List<String> values = getParameterValues(name);
if (values == null || values.isEmpty())
return null;
// Return first parameter value arbitrarily
return values.get(0);
}
@Override
public List<String> getParameterValues(String name) {
return handshakeParameters.get(name);
}
}

View File

@@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility;
import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest;
/**
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
@@ -37,7 +38,7 @@ public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunn
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException {
return BasicTunnelRequestUtility.createTunnel(request);
return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request));
};
}