GUAC-919: Remove SessionKeepAlive (not needed without HttpSession). Consider sessions to be active so long as they have associated tunnels.

This commit is contained in:
Michael Jumper
2014-11-01 23:54:13 -07:00
parent 6fee9cb580
commit 2c476d4d72
7 changed files with 95 additions and 140 deletions

View File

@@ -26,7 +26,10 @@ import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.auth.Credentials;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties;
@@ -60,6 +63,11 @@ public class GuacamoleSession {
*/
private final ClipboardState clipboardState = new ClipboardState();
/**
* All currently-active tunnels, indexed by tunnel UUID.
*/
private final Map<String, GuacamoleTunnel> tunnels = new ConcurrentHashMap<String, GuacamoleTunnel>();
/**
* Creates a new Guacamole session associated with the given user context.
*
@@ -157,5 +165,49 @@ public class GuacamoleSession {
public Collection<Object> getListeners() {
return Collections.unmodifiableCollection(listeners);
}
/**
* Returns whether this session has any associated active tunnels.
*
* @return true if this session has any associated active tunnels,
* false otherwise.
*/
public boolean hasTunnels() {
return !tunnels.isEmpty();
}
/**
* Returns a map of all active tunnels associated with this session, where
* each key is the String representation of the tunnel's UUID. Changes to
* this map immediately affect the set of tunnels associated with this
* session. A tunnel need not be present here to be used by the user
* associated with this session, but tunnels not in this set will not
* be taken into account when determining whether a session is in use.
*
* @return A map of all active tunnels associated with this session.
*/
public Map<String, GuacamoleTunnel> getTunnels() {
return tunnels;
}
/**
* Associates the given tunnel with this session, such that it is taken
* into account when determining session activity.
*
* @param tunnel The tunnel to associate with this session.
*/
public void addTunnel(GuacamoleTunnel tunnel) {
tunnels.put(tunnel.getUUID().toString(), tunnel);
}
/**
* Disassociates the tunnel having the given UUID from this session.
*
* @param uuid The UUID of the tunnel to disassociate from this session.
* @return true if the tunnel existed and was removed, false otherwise.
*/
public boolean removeTunnel(String uuid) {
return tunnels.remove(uuid) != null;
}
}

View File

@@ -1,68 +0,0 @@
/*
* 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 com.google.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* REST service which updates the last access time of the Guacamole session,
* preventing the session from becoming invalid.
*
* @author Michael Jumper
*/
@Path("/keep-alive")
public class SessionKeepAlive {
/**
* A service for authenticating users from auth tokens.
*/
@Inject
private AuthenticationService authenticationService;
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(SessionKeepAlive.class);
@GET
@AuthProviderRESTExposure
public void updateSession(@QueryParam("token") String authToken) throws GuacamoleException {
// Tickle the session
UserContext context = authenticationService.getUserContext(authToken);
// Do nothing
logger.debug("Keep-alive signal received from user \"{}\".", context.self().getUsername());
}
}

View File

@@ -25,7 +25,6 @@ package org.glyptodon.guacamole.net.basic;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService;
import java.util.Collection;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
@@ -35,7 +34,6 @@ import org.glyptodon.guacamole.net.GuacamoleSocket;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.Credentials;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
@@ -72,12 +70,10 @@ public class TunnelRequestService {
private AuthenticationService authenticationService;
/**
* Notifies all listeners in the given collection that a tunnel has been
* Notifies all listeners in the given session that a tunnel has been
* connected.
*
* @param listeners A collection of all listeners that should be notified.
* @param context The UserContext associated with the current session.
* @param credentials The credentials associated with the current session.
* @param session The session associated with the listeners to be notified.
* @param tunnel The tunnel being connected.
* @return true if all listeners are allowing the tunnel to connect,
* or if there are no listeners, and false if any listener is
@@ -88,16 +84,17 @@ public class TunnelRequestService {
* error, the connect is canceled, and no other
* listeners will run.
*/
private boolean notifyConnect(Collection listeners, UserContext context,
Credentials credentials, GuacamoleTunnel tunnel)
private boolean notifyConnect(GuacamoleSession session, GuacamoleTunnel tunnel)
throws GuacamoleException {
// Build event for auth success
TunnelConnectEvent event = new TunnelConnectEvent(context,
credentials, tunnel);
TunnelConnectEvent event = new TunnelConnectEvent(
session.getUserContext(),
session.getCredentials(),
tunnel);
// Notify all listeners
for (Object listener : listeners) {
for (Object listener : session.getListeners()) {
if (listener instanceof TunnelConnectListener) {
// Cancel immediately if hook returns false
@@ -112,12 +109,10 @@ public class TunnelRequestService {
}
/**
* Notifies all listeners in the given collection that a tunnel has been
* Notifies all listeners in the given session that a tunnel has been
* closed.
*
* @param listeners A collection of all listeners that should be notified.
* @param context The UserContext associated with the current session.
* @param credentials The credentials associated with the current session.
* @param session The session associated with the listeners to be notified.
* @param tunnel The tunnel being closed.
* @return true if all listeners are allowing the tunnel to close,
* or if there are no listeners, and false if any listener is
@@ -128,16 +123,17 @@ public class TunnelRequestService {
* error, the close is canceled, and no other
* listeners will run.
*/
private boolean notifyClose(Collection listeners, UserContext context,
Credentials credentials, GuacamoleTunnel tunnel)
private boolean notifyClose(GuacamoleSession session, GuacamoleTunnel tunnel)
throws GuacamoleException {
// Build event for auth success
TunnelCloseEvent event = new TunnelCloseEvent(context,
credentials, tunnel);
TunnelCloseEvent event = new TunnelCloseEvent(
session.getUserContext(),
session.getCredentials(),
tunnel);
// Notify all listeners
for (Object listener : listeners) {
for (Object listener : session.getListeners()) {
if (listener instanceof TunnelCloseListener) {
// Cancel immediately if hook returns false
@@ -165,8 +161,8 @@ public class TunnelRequestService {
// Get auth token and session
String authToken = request.getParameter("authToken");
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
final GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
// Get ID of connection
String id = request.getParameter("id");
TunnelRequest.IdentifierType id_type = TunnelRequest.IdentifierType.getType(id);
@@ -178,18 +174,6 @@ public class TunnelRequestService {
// Remove prefix
id = id.substring(id_type.PREFIX.length());
// Get session-specific elements
final Credentials credentials = session.getCredentials();
final UserContext context = session.getUserContext();
final Collection listeners = session.getListeners();
// If no context or no credentials, not logged in
if (context == null || credentials == null)
throw new GuacamoleSecurityException("Cannot connect - user not logged in.");
// Get clipboard
final ClipboardState clipboard = session.getClipboardState();
// Get client information
GuacamoleClientInformation info = new GuacamoleClientInformation();
@@ -225,6 +209,8 @@ public class TunnelRequestService {
// Connection identifiers
case CONNECTION: {
UserContext context = session.getUserContext();
// Get connection directory
Directory<String, Connection> directory =
context.getRootConnectionGroup().getConnectionDirectory();
@@ -245,6 +231,8 @@ public class TunnelRequestService {
// Connection group identifiers
case CONNECTION_GROUP: {
UserContext context = session.getUserContext();
// Get connection group directory
Directory<String, ConnectionGroup> directory =
context.getRootConnectionGroup().getConnectionGroupDirectory();
@@ -271,6 +259,11 @@ public class TunnelRequestService {
// Associate socket with tunnel
GuacamoleTunnel tunnel = new GuacamoleTunnel(socket) {
/**
* The current clipboard state.
*/
private final ClipboardState clipboard = session.getClipboardState();
@Override
public GuacamoleReader acquireReader() {
@@ -293,9 +286,11 @@ public class TunnelRequestService {
public void close() throws GuacamoleException {
// Only close if not canceled
if (!notifyClose(listeners, context, credentials, this))
if (!notifyClose(session, this))
throw new GuacamoleException("Tunnel close canceled by listener.");
session.removeTunnel(getUUID().toString());
// Close if no exception due to listener
super.close();
@@ -304,7 +299,7 @@ public class TunnelRequestService {
};
// Notify listeners about connection
if (!notifyConnect(listeners, context, credentials, tunnel)) {
if (!notifyConnect(session, tunnel)) {
logger.info("Successful connection canceled by hook.");
return null;
}

View File

@@ -26,7 +26,6 @@ import com.google.inject.Scopes;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
import org.glyptodon.guacamole.net.basic.SessionKeepAlive;
import org.glyptodon.guacamole.net.basic.rest.auth.LoginRESTService;
import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService;
import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService;
@@ -51,7 +50,6 @@ public class RESTServletModule extends ServletModule {
bind(ConnectionGroupRESTService.class);
bind(PermissionRESTService.class);
bind(ProtocolRESTService.class);
bind(SessionKeepAlive.class);
bind(UserRESTService.class);
bind(LoginRESTService.class);

View File

@@ -104,8 +104,16 @@ public class BasicTokenSessionMap implements TokenSessionMap {
* @param authToken The auth token for the session.
* @return True if the session has timed out, false otherwise.
*/
private boolean sessionHasTimedOut(String authToken) {
private boolean isSessionActive(String authToken) {
GuacamoleSession session = sessionMap.get(authToken);
if (session == null)
return false;
// A session is active if it has any active tunnels
if (session.hasTunnels())
return true;
if (!lastAccessTimeMap.containsKey(authToken))
return true;
@@ -120,7 +128,7 @@ public class BasicTokenSessionMap implements TokenSessionMap {
public GuacamoleSession get(String authToken) {
// If the session has timed out, evict the token and force the user to log in again
if (sessionHasTimedOut(authToken)) {
if (isSessionActive(authToken)) {
evict(authToken);
return null;
}

View File

@@ -1199,11 +1199,6 @@ GuacUI.Client.connect = function() {
connect_string += "&video=" + encodeURIComponent(mimetype);
});
// Ping server occasionally to keep HTTP session alive
var session_keep_alive = window.setInterval(function _session_keep_alive() {
GuacamoleService.KeepAlive.ping();
}, GuacUI.Client.KEEP_ALIVE_INTERVAL);
// Show connection errors from tunnel
tunnel.onerror = function(status) {
var message = GuacUI.Client.tunnel_errors[status.code] || GuacUI.Client.tunnel_errors.DEFAULT;
@@ -1216,13 +1211,11 @@ GuacUI.Client.connect = function() {
// Handle disconnect
if (state === Guacamole.Tunnel.State.CLOSED) {
// No need for a keep-alive ping if the tunnel is closed
window.clearInterval(session_keep_alive);
// Notify of disconnections (if not already notified of something else)
if (!GuacUI.Client.visibleStatus)
GuacUI.Client.showStatus("Disconnected",
"You have been disconnected. Reload the page to reconnect.");
}
};

View File

@@ -1056,29 +1056,6 @@ GuacamoleService.Clipboard = {
};
/**
* Collection of service functions which deal with the session keep-alive. Each
* function makes an explicit HTTP query to the server. In the case of the
* keep-alive ping, no response is expected, and any received response is
* ignored.
*/
GuacamoleService.KeepAlive = {
"ping" : function() {
// Construct request URL
var ping_url = "api/keep-alive"
+ "?token=" + GuacamoleService.Auth.current().authToken;
// Send keep-alive "ping"
var xhr = new XMLHttpRequest();
xhr.open("GET", ping_url, true);
xhr.send(null);
}
};
/**
* Collection of service functions which deal with authentication. Note that,
* unlike everything else here, not all functions in GuacamoleService.Auth