mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 21:27:40 +00:00
GUAC-919: Implement period cleanup of sessions. Simplify TokenSessionMap implementation.
This commit is contained in:
@@ -26,6 +26,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
@@ -68,6 +69,11 @@ public class GuacamoleSession {
|
||||
*/
|
||||
private final Map<String, GuacamoleTunnel> tunnels = new ConcurrentHashMap<String, GuacamoleTunnel>();
|
||||
|
||||
/**
|
||||
* The last time this session was accessed.
|
||||
*/
|
||||
private long lastAccessedTime;
|
||||
|
||||
/**
|
||||
* Creates a new Guacamole session associated with the given user context.
|
||||
*
|
||||
@@ -78,6 +84,7 @@ public class GuacamoleSession {
|
||||
*/
|
||||
public GuacamoleSession(Credentials credentials, UserContext userContext) throws GuacamoleException {
|
||||
|
||||
this.lastAccessedTime = System.currentTimeMillis();
|
||||
this.credentials = credentials;
|
||||
this.userContext = userContext;
|
||||
|
||||
@@ -210,4 +217,22 @@ public class GuacamoleSession {
|
||||
return tunnels.remove(uuid) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this session, marking it as accessed.
|
||||
*/
|
||||
public void access() {
|
||||
lastAccessedTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was last accessed, as the number of
|
||||
* milliseconds since midnight January 1, 1970 GMT. Session access must
|
||||
* be explicitly marked through calls to the access() function.
|
||||
*
|
||||
* @return The time this session was last accessed.
|
||||
*/
|
||||
public long getLastAccessedTime() {
|
||||
return lastAccessedTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -160,8 +160,8 @@ public class TunnelRequestService {
|
||||
throws GuacamoleException {
|
||||
|
||||
// Get auth token and session
|
||||
String authToken = request.getParameter("authToken");
|
||||
final GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
final String authToken = request.getParameter("authToken");
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Get ID of connection
|
||||
String id = request.getParameter("id");
|
||||
@@ -259,18 +259,19 @@ 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() {
|
||||
|
||||
// Monitor instructions which pertain to server-side events, if necessary
|
||||
try {
|
||||
if (GuacamoleProperties.getProperty(ClipboardRESTService.INTEGRATION_ENABLED, false))
|
||||
if (GuacamoleProperties.getProperty(ClipboardRESTService.INTEGRATION_ENABLED, false)) {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
ClipboardState clipboard = session.getClipboardState();
|
||||
|
||||
return new MonitoringGuacamoleReader(clipboard, super.acquireReader());
|
||||
|
||||
}
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.warn("Clipboard integration failed to initialize: {}", e.getMessage());
|
||||
@@ -285,6 +286,8 @@ public class TunnelRequestService {
|
||||
@Override
|
||||
public void close() throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||
|
||||
// Only close if not canceled
|
||||
if (!notifyClose(session, this))
|
||||
throw new GuacamoleException("Tunnel close canceled by listener.");
|
||||
|
@@ -22,9 +22,13 @@
|
||||
|
||||
package org.glyptodon.guacamole.net.basic.rest.auth;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties;
|
||||
@@ -46,106 +50,118 @@ public class BasicTokenSessionMap implements TokenSessionMap {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BasicTokenSessionMap.class);
|
||||
|
||||
/**
|
||||
* The last time a user with a specific auth token accessed the API.
|
||||
* Executor service which runs the period session eviction task.
|
||||
*/
|
||||
private final Map<String, Long> lastAccessTimeMap = new HashMap<String, Long>();
|
||||
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
|
||||
|
||||
/**
|
||||
* Keeps track of the authToken to GuacamoleSession mapping.
|
||||
*/
|
||||
private final Map<String, GuacamoleSession> sessionMap = new HashMap<String, GuacamoleSession>();
|
||||
|
||||
/**
|
||||
* The session timeout configuration for an API session, in milliseconds.
|
||||
*/
|
||||
private final long SESSION_TIMEOUT;
|
||||
private final Map<String, GuacamoleSession> sessionMap =
|
||||
Collections.synchronizedMap(new LinkedHashMap<String, GuacamoleSession>(16, 0.75f, true));
|
||||
|
||||
/**
|
||||
* Create a new BasicTokenGuacamoleSessionMap and initialize the session timeout value.
|
||||
*/
|
||||
public BasicTokenSessionMap() {
|
||||
|
||||
// Set up the SESSION_TIMEOUT value, with a one hour default.
|
||||
long sessionTimeoutValue;
|
||||
|
||||
// Read session timeout from guacamole.properties
|
||||
try {
|
||||
sessionTimeoutValue = GuacamoleProperties.getProperty(BasicGuacamoleProperties.API_SESSION_TIMEOUT, 3600000l);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unexpected GuacamoleException caught while reading API_SESSION_TIMEOUT property. Defaulting to 1 hour.", e);
|
||||
logger.error("Unable to read guacamole.properties: {}", e.getMessage());
|
||||
logger.debug("Error while reading session timeout value.", e);
|
||||
sessionTimeoutValue = 3600000l;
|
||||
}
|
||||
|
||||
SESSION_TIMEOUT = sessionTimeoutValue;
|
||||
// Check for expired sessions every minute
|
||||
executor.scheduleAtFixedRate(new SessionEvictionTask(sessionTimeoutValue), 1, 1, TimeUnit.MINUTES);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict an authentication token from the map of logged in users and last
|
||||
* access times.
|
||||
*
|
||||
* @param authToken The authentication token to evict.
|
||||
* Task which iterates through all active sessions, evicting those sessions
|
||||
* which are beyond the session timeout. This is a fairly easy thing to do,
|
||||
* since the session storage structure guarantees that sessions are always
|
||||
* in descending order of age.
|
||||
*/
|
||||
private void evict(String authToken) {
|
||||
sessionMap.remove(authToken);
|
||||
lastAccessTimeMap.remove(authToken);
|
||||
}
|
||||
private class SessionEvictionTask implements Runnable {
|
||||
|
||||
/**
|
||||
* Log that the user represented by this auth token has just used the API.
|
||||
*
|
||||
* @param authToken The authentication token to record access time for.
|
||||
*/
|
||||
private void logAccessTime(String authToken) {
|
||||
lastAccessTimeMap.put(authToken, new Date().getTime());
|
||||
}
|
||||
/**
|
||||
* The maximum allowed age of any session, in milliseconds.
|
||||
*/
|
||||
private final long sessionTimeout;
|
||||
|
||||
/**
|
||||
* Check if a session has timed out.
|
||||
* @param authToken The auth token for the session.
|
||||
* @return True if the session has timed out, false otherwise.
|
||||
*/
|
||||
private boolean isSessionActive(String authToken) {
|
||||
/**
|
||||
* Creates a new task which automatically evicts sessions which are
|
||||
* older than the specified timeout.
|
||||
*
|
||||
* @param sessionTimeout The maximum age of any session, in
|
||||
* milliseconds.
|
||||
*/
|
||||
public SessionEvictionTask(long sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
}
|
||||
|
||||
GuacamoleSession session = sessionMap.get(authToken);
|
||||
if (session == null)
|
||||
return false;
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// A session is active if it has any active tunnels
|
||||
if (session.hasTunnels())
|
||||
return true;
|
||||
// Get current time
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (!lastAccessTimeMap.containsKey(authToken))
|
||||
return true;
|
||||
logger.debug("Checking for expired sessions...");
|
||||
|
||||
long lastAccessTime = lastAccessTimeMap.get(authToken);
|
||||
long currentTime = new Date().getTime();
|
||||
// For each session, remove sesions which have expired
|
||||
Iterator<Map.Entry<String, GuacamoleSession>> entries = sessionMap.entrySet().iterator();
|
||||
while (entries.hasNext()) {
|
||||
|
||||
return currentTime - lastAccessTime > SESSION_TIMEOUT;
|
||||
Map.Entry<String, GuacamoleSession> entry = entries.next();
|
||||
GuacamoleSession session = entry.getValue();
|
||||
|
||||
// Get elapsed time since last access
|
||||
long age = now - session.getLastAccessedTime();
|
||||
|
||||
// If session is too old, evict it and check the next one
|
||||
if (age >= sessionTimeout) {
|
||||
logger.debug("Session \"{}\" has timed out.", entry.getKey());
|
||||
entries.remove();
|
||||
}
|
||||
|
||||
// Otherwise, no other sessions can possibly be old enough
|
||||
else
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
logger.debug("Session check complete.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleSession get(String authToken) {
|
||||
|
||||
// If the session has timed out, evict the token and force the user to log in again
|
||||
if (isSessionActive(authToken)) {
|
||||
evict(authToken);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update the last access time and return the GuacamoleSession
|
||||
logAccessTime(authToken);
|
||||
return sessionMap.get(authToken);
|
||||
GuacamoleSession session = sessionMap.get(authToken);
|
||||
if (session != null)
|
||||
session.access();
|
||||
|
||||
return session;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String authToken, GuacamoleSession session) {
|
||||
|
||||
// Update the last access time, and create the token/GuacamoleSession mapping
|
||||
logAccessTime(authToken);
|
||||
sessionMap.put(authToken, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -51,4 +51,10 @@ public interface TokenSessionMap {
|
||||
*/
|
||||
public GuacamoleSession get(String authToken);
|
||||
|
||||
/**
|
||||
* Shuts down this session map, disallowing future sessions and reclaiming
|
||||
* any resources.
|
||||
*/
|
||||
public void shutdown();
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user