From b3319b817d57dfb46f87d879130c85bfc2921afe Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 2 Oct 2022 11:58:32 -0700 Subject: [PATCH] GUACAMOLE-1224: Add user session invalidation/logout event. --- .../event/UserSessionInvalidatedEvent.java | 27 ++++++++++++++ .../apache/guacamole/GuacamoleSession.java | 37 +++++++++++++++---- .../guacamole/event/EventLoggingListener.java | 6 +++ .../guacamole/event/RequestingUser.java | 6 +-- .../rest/auth/AuthenticationService.java | 9 +---- 5 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java new file mode 100644 index 000000000..4398cfa89 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/event/UserSessionInvalidatedEvent.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.net.event; + +/** + * Event that is dispatched when a user has logged out or their session has + * expired. + */ +public interface UserSessionInvalidatedEvent extends UserEvent { +} diff --git a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java index 24ea196c5..3e8d488c3 100644 --- a/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java +++ b/guacamole/src/main/java/org/apache/guacamole/GuacamoleSession.java @@ -23,12 +23,13 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.event.UserSessionInvalidatedEvent; import org.apache.guacamole.rest.auth.DecoratedUserContext; +import org.apache.guacamole.rest.event.ListenerService; import org.apache.guacamole.tunnel.UserTunnel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,8 +59,12 @@ public class GuacamoleSession { /** * All currently-active tunnels, indexed by tunnel UUID. */ - private final Map tunnels = - new ConcurrentHashMap(); + private final Map tunnels = new ConcurrentHashMap<>(); + + /** + * Service for dispatching events to registered event listeners. + */ + private final ListenerService listenerService; /** * The last time this session was accessed. @@ -70,9 +75,9 @@ public class GuacamoleSession { * Creates a new Guacamole session associated with the given * AuthenticatedUser and UserContexts. * - * @param environment - * The environment of the Guacamole server associated with this new - * session. + * @param listenerService + * The service to use to notify registered event listeners when this + * session is invalidated. * * @param authenticatedUser * The authenticated user to associate this session with. @@ -83,11 +88,12 @@ public class GuacamoleSession { * @throws GuacamoleException * If an error prevents the session from being created. */ - public GuacamoleSession(Environment environment, + public GuacamoleSession(ListenerService listenerService, AuthenticatedUser authenticatedUser, List userContexts) throws GuacamoleException { this.lastAccessedTime = System.currentTimeMillis(); + this.listenerService = listenerService; this.authenticatedUser = authenticatedUser; this.userContexts = userContexts; } @@ -260,6 +266,23 @@ public class GuacamoleSession { // Invalidate the authenticated user object authenticatedUser.invalidate(); + // Advise any registered listeners that the user's session is now + // invalidated + try { + listenerService.handleEvent(new UserSessionInvalidatedEvent() { + + @Override + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; + } + + }); + } + catch (GuacamoleException e) { + logger.error("An extension listening for session invalidation failed: {}", e.getMessage()); + logger.debug("Extension failed internally while handling the session invalidation event.", e); + } + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java index f3b804892..85d616cbb 100644 --- a/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/event/EventLoggingListener.java @@ -27,6 +27,7 @@ import org.apache.guacamole.net.event.ApplicationStartedEvent; import org.apache.guacamole.net.event.DirectoryEvent; import org.apache.guacamole.net.event.DirectoryFailureEvent; import org.apache.guacamole.net.event.DirectorySuccessEvent; +import org.apache.guacamole.net.event.UserSessionInvalidatedEvent; import org.apache.guacamole.net.event.listener.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -123,6 +124,11 @@ public class EventLoggingListener implements Listener { else if (event instanceof DirectoryFailureEvent) logFailure((DirectoryFailureEvent) event); + // Logout / session expiration + else if (event instanceof UserSessionInvalidatedEvent) + logger.info("{} has logged out, or their session has expired or " + + "been terminated.", new RequestingUser((UserSessionInvalidatedEvent) event)); + // Application startup/shutdown else if (event instanceof ApplicationStartedEvent) logger.info("The Apache Guacamole web application has started."); diff --git a/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java b/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java index 124aa8f80..bcc14f4c4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java +++ b/guacamole/src/main/java/org/apache/guacamole/event/RequestingUser.java @@ -20,7 +20,7 @@ package org.apache.guacamole.event; import org.apache.guacamole.net.auth.AuthenticatedUser; -import org.apache.guacamole.net.event.DirectoryEvent; +import org.apache.guacamole.net.event.UserEvent; /** * Loggable representation of the user that requested an operation. @@ -30,7 +30,7 @@ public class RequestingUser implements LoggableDetail { /** * The event representing the requested operation. */ - private final DirectoryEvent event; + private final UserEvent event; /** * Creates a new RequestingUser that represents the user that requested the @@ -39,7 +39,7 @@ public class RequestingUser implements LoggableDetail { * @param event * The event representing the requested operation. */ - public RequestingUser(DirectoryEvent event) { + public RequestingUser(UserEvent event) { this.event = event; } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index 705d7a856..48a2d5a36 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -30,7 +30,6 @@ import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.GuacamoleSession; -import org.apache.guacamole.environment.Environment; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -56,12 +55,6 @@ public class AuthenticationService { */ private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); - /** - * The Guacamole server environment. - */ - @Inject - private Environment environment; - /** * All configured authentication providers which can be used to * authenticate users or retrieve data associated with authenticated users. @@ -443,7 +436,7 @@ public class AuthenticationService { // If no existing session, generate a new token/session pair else { authToken = authTokenGenerator.getToken(); - tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts)); + tokenSessionMap.put(authToken, new GuacamoleSession(listenerService, authenticatedUser, userContexts)); logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier()); }