mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 13:41:21 +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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.glyptodon.guacamole.GuacamoleException;
|
import org.glyptodon.guacamole.GuacamoleException;
|
||||||
@@ -68,6 +69,11 @@ public class GuacamoleSession {
|
|||||||
*/
|
*/
|
||||||
private final Map<String, GuacamoleTunnel> tunnels = new ConcurrentHashMap<String, GuacamoleTunnel>();
|
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.
|
* 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 {
|
public GuacamoleSession(Credentials credentials, UserContext userContext) throws GuacamoleException {
|
||||||
|
|
||||||
|
this.lastAccessedTime = System.currentTimeMillis();
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
this.userContext = userContext;
|
this.userContext = userContext;
|
||||||
|
|
||||||
@@ -210,4 +217,22 @@ public class GuacamoleSession {
|
|||||||
return tunnels.remove(uuid) != null;
|
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 {
|
throws GuacamoleException {
|
||||||
|
|
||||||
// Get auth token and session
|
// Get auth token and session
|
||||||
String authToken = request.getParameter("authToken");
|
final String authToken = request.getParameter("authToken");
|
||||||
final GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||||
|
|
||||||
// Get ID of connection
|
// Get ID of connection
|
||||||
String id = request.getParameter("id");
|
String id = request.getParameter("id");
|
||||||
@@ -259,18 +259,19 @@ public class TunnelRequestService {
|
|||||||
// Associate socket with tunnel
|
// Associate socket with tunnel
|
||||||
GuacamoleTunnel tunnel = new GuacamoleTunnel(socket) {
|
GuacamoleTunnel tunnel = new GuacamoleTunnel(socket) {
|
||||||
|
|
||||||
/**
|
|
||||||
* The current clipboard state.
|
|
||||||
*/
|
|
||||||
private final ClipboardState clipboard = session.getClipboardState();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GuacamoleReader acquireReader() {
|
public GuacamoleReader acquireReader() {
|
||||||
|
|
||||||
// Monitor instructions which pertain to server-side events, if necessary
|
// Monitor instructions which pertain to server-side events, if necessary
|
||||||
try {
|
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());
|
return new MonitoringGuacamoleReader(clipboard, super.acquireReader());
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (GuacamoleException e) {
|
catch (GuacamoleException e) {
|
||||||
logger.warn("Clipboard integration failed to initialize: {}", e.getMessage());
|
logger.warn("Clipboard integration failed to initialize: {}", e.getMessage());
|
||||||
@@ -285,6 +286,8 @@ public class TunnelRequestService {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws GuacamoleException {
|
public void close() throws GuacamoleException {
|
||||||
|
|
||||||
|
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||||
|
|
||||||
// Only close if not canceled
|
// Only close if not canceled
|
||||||
if (!notifyClose(session, this))
|
if (!notifyClose(session, this))
|
||||||
throw new GuacamoleException("Tunnel close canceled by listener.");
|
throw new GuacamoleException("Tunnel close canceled by listener.");
|
||||||
|
@@ -22,9 +22,13 @@
|
|||||||
|
|
||||||
package org.glyptodon.guacamole.net.basic.rest.auth;
|
package org.glyptodon.guacamole.net.basic.rest.auth;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
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.GuacamoleException;
|
||||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||||
import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties;
|
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);
|
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.
|
* Keeps track of the authToken to GuacamoleSession mapping.
|
||||||
*/
|
*/
|
||||||
private final Map<String, GuacamoleSession> sessionMap = new HashMap<String, GuacamoleSession>();
|
private final Map<String, GuacamoleSession> sessionMap =
|
||||||
|
Collections.synchronizedMap(new LinkedHashMap<String, GuacamoleSession>(16, 0.75f, true));
|
||||||
/**
|
|
||||||
* The session timeout configuration for an API session, in milliseconds.
|
|
||||||
*/
|
|
||||||
private final long SESSION_TIMEOUT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new BasicTokenGuacamoleSessionMap and initialize the session timeout value.
|
* Create a new BasicTokenGuacamoleSessionMap and initialize the session timeout value.
|
||||||
*/
|
*/
|
||||||
public BasicTokenSessionMap() {
|
public BasicTokenSessionMap() {
|
||||||
|
|
||||||
// Set up the SESSION_TIMEOUT value, with a one hour default.
|
|
||||||
long sessionTimeoutValue;
|
long sessionTimeoutValue;
|
||||||
|
|
||||||
|
// Read session timeout from guacamole.properties
|
||||||
try {
|
try {
|
||||||
sessionTimeoutValue = GuacamoleProperties.getProperty(BasicGuacamoleProperties.API_SESSION_TIMEOUT, 3600000l);
|
sessionTimeoutValue = GuacamoleProperties.getProperty(BasicGuacamoleProperties.API_SESSION_TIMEOUT, 3600000l);
|
||||||
}
|
}
|
||||||
catch (GuacamoleException e) {
|
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;
|
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
|
* Task which iterates through all active sessions, evicting those sessions
|
||||||
* access times.
|
* which are beyond the session timeout. This is a fairly easy thing to do,
|
||||||
*
|
* since the session storage structure guarantees that sessions are always
|
||||||
* @param authToken The authentication token to evict.
|
* in descending order of age.
|
||||||
*/
|
*/
|
||||||
private void evict(String authToken) {
|
private class SessionEvictionTask implements Runnable {
|
||||||
sessionMap.remove(authToken);
|
|
||||||
lastAccessTimeMap.remove(authToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log that the user represented by this auth token has just used the API.
|
* The maximum allowed age of any session, in milliseconds.
|
||||||
*
|
*/
|
||||||
* @param authToken The authentication token to record access time for.
|
private final long sessionTimeout;
|
||||||
*/
|
|
||||||
private void logAccessTime(String authToken) {
|
|
||||||
lastAccessTimeMap.put(authToken, new Date().getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a session has timed out.
|
* Creates a new task which automatically evicts sessions which are
|
||||||
* @param authToken The auth token for the session.
|
* older than the specified timeout.
|
||||||
* @return True if the session has timed out, false otherwise.
|
*
|
||||||
*/
|
* @param sessionTimeout The maximum age of any session, in
|
||||||
private boolean isSessionActive(String authToken) {
|
* milliseconds.
|
||||||
|
*/
|
||||||
|
public SessionEvictionTask(long sessionTimeout) {
|
||||||
|
this.sessionTimeout = sessionTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
GuacamoleSession session = sessionMap.get(authToken);
|
@Override
|
||||||
if (session == null)
|
public void run() {
|
||||||
return false;
|
|
||||||
|
|
||||||
// A session is active if it has any active tunnels
|
// Get current time
|
||||||
if (session.hasTunnels())
|
long now = System.currentTimeMillis();
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!lastAccessTimeMap.containsKey(authToken))
|
logger.debug("Checking for expired sessions...");
|
||||||
return true;
|
|
||||||
|
|
||||||
long lastAccessTime = lastAccessTimeMap.get(authToken);
|
// For each session, remove sesions which have expired
|
||||||
long currentTime = new Date().getTime();
|
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
|
@Override
|
||||||
public GuacamoleSession get(String authToken) {
|
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
|
// Update the last access time and return the GuacamoleSession
|
||||||
logAccessTime(authToken);
|
GuacamoleSession session = sessionMap.get(authToken);
|
||||||
return sessionMap.get(authToken);
|
if (session != null)
|
||||||
|
session.access();
|
||||||
|
|
||||||
|
return session;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void put(String authToken, GuacamoleSession session) {
|
public void put(String authToken, GuacamoleSession session) {
|
||||||
|
|
||||||
// Update the last access time, and create the token/GuacamoleSession mapping
|
|
||||||
logAccessTime(authToken);
|
|
||||||
sessionMap.put(authToken, session);
|
sessionMap.put(authToken, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
executor.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -51,4 +51,10 @@ public interface TokenSessionMap {
|
|||||||
*/
|
*/
|
||||||
public GuacamoleSession get(String authToken);
|
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