mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 00:53: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.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; | ||||
| @@ -44,108 +48,120 @@ public class BasicTokenSessionMap implements TokenSessionMap { | ||||
|      * Logger for this 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. | ||||
|      */ | ||||
|     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. | ||||
|      */ | ||||
|     private void evict(String authToken) { | ||||
|         sessionMap.remove(authToken); | ||||
|         lastAccessTimeMap.remove(authToken); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * 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()); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * 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) { | ||||
|  | ||||
|         GuacamoleSession session = sessionMap.get(authToken); | ||||
|         if (session == null) | ||||
|             return false; | ||||
|     /** | ||||
|      * 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 class SessionEvictionTask implements Runnable { | ||||
|  | ||||
|         // A session is active if it has any active tunnels | ||||
|         if (session.hasTunnels()) | ||||
|             return true; | ||||
|         /** | ||||
|          * The maximum allowed age of any session, in milliseconds. | ||||
|          */ | ||||
|         private final long sessionTimeout; | ||||
|  | ||||
|         /** | ||||
|          * 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; | ||||
|         } | ||||
|          | ||||
|         if (!lastAccessTimeMap.containsKey(authToken)) | ||||
|             return true; | ||||
|          | ||||
|         long lastAccessTime = lastAccessTimeMap.get(authToken); | ||||
|         long currentTime = new Date().getTime(); | ||||
|          | ||||
|         return currentTime - lastAccessTime > SESSION_TIMEOUT; | ||||
|         @Override | ||||
|         public void run() { | ||||
|  | ||||
|             // Get current time | ||||
|             long now = System.currentTimeMillis(); | ||||
|  | ||||
|             logger.debug("Checking for expired sessions..."); | ||||
|              | ||||
|             // For each session, remove sesions which have expired | ||||
|             Iterator<Map.Entry<String, GuacamoleSession>> entries = sessionMap.entrySet().iterator(); | ||||
|             while (entries.hasNext()) { | ||||
|  | ||||
|                 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