GUACAMOLE-1224: Add user session invalidation/logout event.

This commit is contained in:
Michael Jumper
2022-10-02 11:58:32 -07:00
parent 63de886e5d
commit b3319b817d
5 changed files with 67 additions and 18 deletions

View File

@@ -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 {
}

View File

@@ -23,12 +23,13 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.UserContext; 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.auth.DecoratedUserContext;
import org.apache.guacamole.rest.event.ListenerService;
import org.apache.guacamole.tunnel.UserTunnel; import org.apache.guacamole.tunnel.UserTunnel;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -58,8 +59,12 @@ public class GuacamoleSession {
/** /**
* All currently-active tunnels, indexed by tunnel UUID. * All currently-active tunnels, indexed by tunnel UUID.
*/ */
private final Map<String, UserTunnel> tunnels = private final Map<String, UserTunnel> tunnels = new ConcurrentHashMap<>();
new ConcurrentHashMap<String, UserTunnel>();
/**
* Service for dispatching events to registered event listeners.
*/
private final ListenerService listenerService;
/** /**
* The last time this session was accessed. * The last time this session was accessed.
@@ -70,9 +75,9 @@ public class GuacamoleSession {
* Creates a new Guacamole session associated with the given * Creates a new Guacamole session associated with the given
* AuthenticatedUser and UserContexts. * AuthenticatedUser and UserContexts.
* *
* @param environment * @param listenerService
* The environment of the Guacamole server associated with this new * The service to use to notify registered event listeners when this
* session. * session is invalidated.
* *
* @param authenticatedUser * @param authenticatedUser
* The authenticated user to associate this session with. * The authenticated user to associate this session with.
@@ -83,11 +88,12 @@ public class GuacamoleSession {
* @throws GuacamoleException * @throws GuacamoleException
* If an error prevents the session from being created. * If an error prevents the session from being created.
*/ */
public GuacamoleSession(Environment environment, public GuacamoleSession(ListenerService listenerService,
AuthenticatedUser authenticatedUser, AuthenticatedUser authenticatedUser,
List<DecoratedUserContext> userContexts) List<DecoratedUserContext> userContexts)
throws GuacamoleException { throws GuacamoleException {
this.lastAccessedTime = System.currentTimeMillis(); this.lastAccessedTime = System.currentTimeMillis();
this.listenerService = listenerService;
this.authenticatedUser = authenticatedUser; this.authenticatedUser = authenticatedUser;
this.userContexts = userContexts; this.userContexts = userContexts;
} }
@@ -260,6 +266,23 @@ public class GuacamoleSession {
// Invalidate the authenticated user object // Invalidate the authenticated user object
authenticatedUser.invalidate(); 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);
}
} }
} }

View File

@@ -27,6 +27,7 @@ import org.apache.guacamole.net.event.ApplicationStartedEvent;
import org.apache.guacamole.net.event.DirectoryEvent; import org.apache.guacamole.net.event.DirectoryEvent;
import org.apache.guacamole.net.event.DirectoryFailureEvent; import org.apache.guacamole.net.event.DirectoryFailureEvent;
import org.apache.guacamole.net.event.DirectorySuccessEvent; import org.apache.guacamole.net.event.DirectorySuccessEvent;
import org.apache.guacamole.net.event.UserSessionInvalidatedEvent;
import org.apache.guacamole.net.event.listener.Listener; import org.apache.guacamole.net.event.listener.Listener;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -123,6 +124,11 @@ public class EventLoggingListener implements Listener {
else if (event instanceof DirectoryFailureEvent) else if (event instanceof DirectoryFailureEvent)
logFailure((DirectoryFailureEvent<?>) event); 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 // Application startup/shutdown
else if (event instanceof ApplicationStartedEvent) else if (event instanceof ApplicationStartedEvent)
logger.info("The Apache Guacamole web application has started."); logger.info("The Apache Guacamole web application has started.");

View File

@@ -20,7 +20,7 @@
package org.apache.guacamole.event; package org.apache.guacamole.event;
import org.apache.guacamole.net.auth.AuthenticatedUser; 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. * Loggable representation of the user that requested an operation.
@@ -30,7 +30,7 @@ public class RequestingUser implements LoggableDetail {
/** /**
* The event representing the requested operation. * 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 * Creates a new RequestingUser that represents the user that requested the
@@ -39,7 +39,7 @@ public class RequestingUser implements LoggableDetail {
* @param event * @param event
* The event representing the requested operation. * The event representing the requested operation.
*/ */
public RequestingUser(DirectoryEvent<?> event) { public RequestingUser(UserEvent event) {
this.event = event; this.event = event;
} }

View File

@@ -30,7 +30,6 @@ import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.GuacamoleUnauthorizedException;
import org.apache.guacamole.GuacamoleSession; import org.apache.guacamole.GuacamoleSession;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.Credentials;
@@ -56,12 +55,6 @@ public class AuthenticationService {
*/ */
private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); 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 * All configured authentication providers which can be used to
* authenticate users or retrieve data associated with authenticated users. * 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 // If no existing session, generate a new token/session pair
else { else {
authToken = authTokenGenerator.getToken(); 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()); logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
} }