mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUAC-1364: Move session management into AuthenticationService. The token map should not appear anywhere else (except where needed to configure dependency injection of the same).
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
package org.glyptodon.guacamole.net.basic.rest;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import javax.ws.rs.FormParam;
|
||||
@@ -35,8 +36,7 @@ import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.glyptodon.guacamole.net.basic.rest.auth.TokenSessionMap;
|
||||
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -58,22 +58,10 @@ public class RESTExceptionWrapper implements MethodInterceptor {
|
||||
private final Logger logger = LoggerFactory.getLogger(RESTExceptionWrapper.class);
|
||||
|
||||
/**
|
||||
* Singleton instance of TokenSessionMap.
|
||||
* Service for authenticating users and managing their Guacamole sessions.
|
||||
*/
|
||||
private final TokenSessionMap tokenSessionMap;
|
||||
|
||||
/**
|
||||
* Creates an interceptor which automatically handles GuacamoleExceptions
|
||||
* within the REST services, including implicit invalidation of
|
||||
* authentication tokens.
|
||||
*
|
||||
* @param tokenSessionMap
|
||||
* The singleton instance of TokenSessionMap to use if management of
|
||||
* authentication tokens is required to handle a particular error.
|
||||
*/
|
||||
public RESTExceptionWrapper(TokenSessionMap tokenSessionMap) {
|
||||
this.tokenSessionMap = tokenSessionMap;
|
||||
}
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Determines whether the given set of annotations describes an HTTP
|
||||
@@ -178,11 +166,8 @@ public class RESTExceptionWrapper implements MethodInterceptor {
|
||||
String token = getAuthenticationToken(invocation);
|
||||
|
||||
// If there is an associated auth token, invalidate it
|
||||
GuacamoleSession session = tokenSessionMap.remove(token);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
if (authenticationService.destroyGuacamoleSession(token))
|
||||
logger.debug("Implicitly invalidated session for token \"{}\".", token);
|
||||
}
|
||||
|
||||
// Continue with exception processing
|
||||
throw e;
|
||||
|
@@ -26,6 +26,7 @@ import com.google.inject.Scopes;
|
||||
import com.google.inject.matcher.Matchers;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
|
||||
import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService;
|
||||
import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService;
|
||||
@@ -77,11 +78,9 @@ public class RESTServiceModule extends ServletModule {
|
||||
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
||||
|
||||
// Automatically translate GuacamoleExceptions for REST methods
|
||||
bindInterceptor(
|
||||
Matchers.any(),
|
||||
new RESTMethodMatcher(),
|
||||
new RESTExceptionWrapper(tokenSessionMap)
|
||||
);
|
||||
MethodInterceptor interceptor = new RESTExceptionWrapper();
|
||||
requestInjection(interceptor);
|
||||
bindInterceptor(Matchers.any(), new RESTMethodMatcher(), interceptor);
|
||||
|
||||
// Bind convenience services used by the REST API
|
||||
bind(ObjectRetrievalService.class);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Glyptodon LLC
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -23,30 +23,391 @@
|
||||
package org.glyptodon.guacamole.net.basic.rest.auth;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.glyptodon.guacamole.environment.Environment;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A service for performing authentication checks in REST endpoints.
|
||||
*
|
||||
* @author James Muehlner
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class AuthenticationService {
|
||||
|
||||
|
||||
/**
|
||||
* Logger for this 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
|
||||
* authenticate users or retrieve data associated with authenticated users.
|
||||
*/
|
||||
@Inject
|
||||
private List<AuthenticationProvider> authProviders;
|
||||
|
||||
/**
|
||||
* The map of auth tokens to sessions for the REST endpoints.
|
||||
*/
|
||||
@Inject
|
||||
private TokenSessionMap tokenSessionMap;
|
||||
|
||||
|
||||
/**
|
||||
* A generator for creating new auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthTokenGenerator authTokenGenerator;
|
||||
|
||||
/**
|
||||
* Regular expression which matches any IPv4 address.
|
||||
*/
|
||||
private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})";
|
||||
|
||||
/**
|
||||
* Regular expression which matches any IPv6 address.
|
||||
*/
|
||||
private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})";
|
||||
|
||||
/**
|
||||
* Regular expression which matches any IP address, regardless of version.
|
||||
*/
|
||||
private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")";
|
||||
|
||||
/**
|
||||
* Pattern which matches valid values of the de-facto standard
|
||||
* "X-Forwarded-For" header.
|
||||
*/
|
||||
private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$");
|
||||
|
||||
/**
|
||||
* Returns a formatted string containing an IP address, or list of IP
|
||||
* addresses, which represent the HTTP client and any involved proxies. As
|
||||
* the headers used to determine proxies can easily be forged, this data is
|
||||
* superficially validated to ensure that it at least looks like a list of
|
||||
* IPs.
|
||||
*
|
||||
* @param request
|
||||
* The HTTP request to format.
|
||||
*
|
||||
* @return
|
||||
* A formatted string containing one or more IP addresses.
|
||||
*/
|
||||
private String getLoggableAddress(HttpServletRequest request) {
|
||||
|
||||
// Log X-Forwarded-For, if present and valid
|
||||
String header = request.getHeader("X-Forwarded-For");
|
||||
if (header != null && X_FORWARDED_FOR.matcher(header).matches())
|
||||
return "[" + header + ", " + request.getRemoteAddr() + "]";
|
||||
|
||||
// If header absent or invalid, just use source IP
|
||||
return request.getRemoteAddr();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts authentication against all AuthenticationProviders, in order,
|
||||
* using the provided credentials. The first authentication failure takes
|
||||
* priority, but remaining AuthenticationProviders are attempted. If any
|
||||
* AuthenticationProvider succeeds, the resulting AuthenticatedUser is
|
||||
* returned, and no further AuthenticationProviders are tried.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use for authentication.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticatedUser given by the highest-priority
|
||||
* AuthenticationProvider for which the given credentials are valid.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the given credentials are not valid for any
|
||||
* AuthenticationProvider, or if an error occurs while authenticating
|
||||
* the user.
|
||||
*/
|
||||
private AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleCredentialsException authFailure = null;
|
||||
|
||||
// Attempt authentication against each AuthenticationProvider
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Attempt authentication
|
||||
try {
|
||||
AuthenticatedUser authenticatedUser = authProvider.authenticateUser(credentials);
|
||||
if (authenticatedUser != null)
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
// First failure takes priority for now
|
||||
catch (GuacamoleCredentialsException e) {
|
||||
if (authFailure == null)
|
||||
authFailure = e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If a specific failure occured, rethrow that
|
||||
if (authFailure != null)
|
||||
throw authFailure;
|
||||
|
||||
// Otherwise, request standard username/password
|
||||
throw new GuacamoleInvalidCredentialsException(
|
||||
"Permission Denied.",
|
||||
CredentialsInfo.USERNAME_PASSWORD
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-authenticates the given AuthenticatedUser against the
|
||||
* AuthenticationProvider that originally created it, using the given
|
||||
* Credentials.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser to re-authenticate.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials to use to re-authenticate the user.
|
||||
*
|
||||
* @return
|
||||
* A AuthenticatedUser which may have been updated due to re-
|
||||
* authentication.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents the user from being re-authenticated.
|
||||
*/
|
||||
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
// Get original AuthenticationProvider
|
||||
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
||||
|
||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
if (authenticatedUser == null)
|
||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
||||
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthenticatedUser associated with the given session and
|
||||
* credentials, performing a fresh authentication and creating a new
|
||||
* AuthenticatedUser if necessary.
|
||||
*
|
||||
* @param existingSession
|
||||
* The current GuacamoleSession, or null if no session exists yet.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials to use to authenticate the user.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticatedUser associated with the given session and
|
||||
* credentials.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while authenticating or re-authenticating the
|
||||
* user.
|
||||
*/
|
||||
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Re-authenticate user if session exists
|
||||
if (existingSession != null)
|
||||
return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
||||
|
||||
// Otherwise, attempt authentication as a new user
|
||||
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(),
|
||||
getLoggableAddress(credentials.getRequest()));
|
||||
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
// Log and rethrow any authentication errors
|
||||
catch (GuacamoleException e) {
|
||||
|
||||
// Get request and username for sake of logging
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
String username = credentials.getUsername();
|
||||
|
||||
// Log authentication failures with associated usernames
|
||||
if (username != null) {
|
||||
if (logger.isWarnEnabled())
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
||||
getLoggableAddress(request), username);
|
||||
}
|
||||
|
||||
// Log anonymous authentication failures
|
||||
else if (logger.isDebugEnabled())
|
||||
logger.debug("Anonymous authentication attempt from {} failed.",
|
||||
getLoggableAddress(request));
|
||||
|
||||
// Rethrow exception
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with the given AuthenticatedUser,
|
||||
* updating existing UserContexts, if any. If no UserContexts are yet
|
||||
* associated with the given AuthenticatedUser, new UserContexts are
|
||||
* generated by polling each available AuthenticationProvider.
|
||||
*
|
||||
* @param existingSession
|
||||
* The current GuacamoleSession, or null if no session exists yet.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser that has successfully authenticated or re-
|
||||
* authenticated.
|
||||
*
|
||||
* @return
|
||||
* A List of all UserContexts associated with the given
|
||||
* AuthenticatedUser.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating or updating any UserContext.
|
||||
*/
|
||||
private List<UserContext> getUserContexts(GuacamoleSession existingSession,
|
||||
AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||
|
||||
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
|
||||
|
||||
// If UserContexts already exist, update them and add to the list
|
||||
if (existingSession != null) {
|
||||
|
||||
// Update all old user contexts
|
||||
List<UserContext> oldUserContexts = existingSession.getUserContexts();
|
||||
for (UserContext oldUserContext : oldUserContexts) {
|
||||
|
||||
// Update existing UserContext
|
||||
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
||||
UserContext userContext = authProvider.updateUserContext(oldUserContext, authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
// If unsuccessful, log that this happened, as it may be a bug
|
||||
else
|
||||
logger.debug("AuthenticationProvider \"{}\" retroactively destroyed its UserContext.",
|
||||
authProvider.getClass().getName());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, create new UserContexts from available AuthenticationProviders
|
||||
else {
|
||||
|
||||
// Get UserContexts from each available AuthenticationProvider
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Generate new UserContext
|
||||
UserContext userContext = authProvider.getUserContext(authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return userContexts;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user using the given credentials and optional
|
||||
* authentication token, returning the authentication token associated with
|
||||
* the user's Guacamole session, which may be newly generated. If an
|
||||
* existing token is provided, the authentication procedure will attempt to
|
||||
* update or reuse the provided token, but it is possible that a new token
|
||||
* will be returned. Note that this function CANNOT return null.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use when authenticating the user.
|
||||
*
|
||||
* @param token
|
||||
* The authentication token to use if attempting to re-authenticate an
|
||||
* existing session, or null to request a new token.
|
||||
*
|
||||
* @return
|
||||
* The authentication token associated with the newly created or
|
||||
* existing session.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the authentication or re-authentication attempt fails.
|
||||
*/
|
||||
public String authenticate(Credentials credentials, String token)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Pull existing session if token provided
|
||||
GuacamoleSession existingSession;
|
||||
if (token != null)
|
||||
existingSession = tokenSessionMap.get(token);
|
||||
else
|
||||
existingSession = null;
|
||||
|
||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
||||
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
||||
List<UserContext> userContexts = getUserContexts(existingSession, authenticatedUser);
|
||||
|
||||
// Update existing session, if it exists
|
||||
String authToken;
|
||||
if (existingSession != null) {
|
||||
authToken = token;
|
||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||
existingSession.setUserContexts(userContexts);
|
||||
}
|
||||
|
||||
// If no existing session, generate a new token/session pair
|
||||
else {
|
||||
authToken = authTokenGenerator.getToken();
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||
}
|
||||
|
||||
return authToken;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Guacamole session for a given auth token, if the auth token
|
||||
* represents a currently logged in user. Throws an unauthorized error
|
||||
* otherwise.
|
||||
*
|
||||
*
|
||||
* @param authToken The auth token to check against the map of logged in users.
|
||||
* @return The session that corresponds to the provided auth token.
|
||||
* @throws GuacamoleException If the auth token does not correspond to any
|
||||
@@ -66,6 +427,32 @@ public class AuthenticationService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates a specific authentication token and its corresponding
|
||||
* Guacamole session, effectively logging out the associated user. If the
|
||||
* authentication token is not valid, this function has no effect.
|
||||
*
|
||||
* @param authToken
|
||||
* The token being invalidated.
|
||||
*
|
||||
* @return
|
||||
* true if the given authentication token was valid and the
|
||||
* corresponding Guacamole session was destroyed, false if the given
|
||||
* authentication token was not valid and no action was taken.
|
||||
*/
|
||||
public boolean destroyGuacamoleSession(String authToken) {
|
||||
|
||||
// Remove corresponding GuacamoleSession if the token is valid
|
||||
GuacamoleSession session = tokenSessionMap.remove(authToken);
|
||||
if (session == null)
|
||||
return false;
|
||||
|
||||
// Invalidate the removed session
|
||||
session.invalidate();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with a given auth token, if the auth
|
||||
* token represents a currently logged in user. Throws an unauthorized
|
||||
|
@@ -26,7 +26,6 @@ import com.google.inject.Inject;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.FormParam;
|
||||
@@ -40,15 +39,9 @@ import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||
import org.glyptodon.guacamole.environment.Environment;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleCredentialsException;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.glyptodon.guacamole.net.basic.rest.APIRequest;
|
||||
import org.slf4j.Logger;
|
||||
@@ -64,81 +57,16 @@ import org.slf4j.LoggerFactory;
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class TokenRESTService {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Inject
|
||||
private List<AuthenticationProvider> authProviders;
|
||||
|
||||
/**
|
||||
* The map of auth tokens to sessions for the REST endpoints.
|
||||
*/
|
||||
@Inject
|
||||
private TokenSessionMap tokenSessionMap;
|
||||
|
||||
/**
|
||||
* A generator for creating new auth tokens.
|
||||
*/
|
||||
@Inject
|
||||
private AuthTokenGenerator authTokenGenerator;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(TokenRESTService.class);
|
||||
|
||||
/**
|
||||
* Regular expression which matches any IPv4 address.
|
||||
* Service for authenticating users and managing their Guacamole sessions.
|
||||
*/
|
||||
private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})";
|
||||
|
||||
/**
|
||||
* Regular expression which matches any IPv6 address.
|
||||
*/
|
||||
private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})";
|
||||
|
||||
/**
|
||||
* Regular expression which matches any IP address, regardless of version.
|
||||
*/
|
||||
private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")";
|
||||
|
||||
/**
|
||||
* Pattern which matches valid values of the de-facto standard
|
||||
* "X-Forwarded-For" header.
|
||||
*/
|
||||
private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$");
|
||||
|
||||
/**
|
||||
* Returns a formatted string containing an IP address, or list of IP
|
||||
* addresses, which represent the HTTP client and any involved proxies. As
|
||||
* the headers used to determine proxies can easily be forged, this data is
|
||||
* superficially validated to ensure that it at least looks like a list of
|
||||
* IPs.
|
||||
*
|
||||
* @param request
|
||||
* The HTTP request to format.
|
||||
*
|
||||
* @return
|
||||
* A formatted string containing one or more IP addresses.
|
||||
*/
|
||||
private String getLoggableAddress(HttpServletRequest request) {
|
||||
|
||||
// Log X-Forwarded-For, if present and valid
|
||||
String header = request.getHeader("X-Forwarded-For");
|
||||
if (header != null && X_FORWARDED_FOR.matcher(header).matches())
|
||||
return "[" + header + ", " + request.getRemoteAddr() + "]";
|
||||
|
||||
// If header absent or invalid, just use source IP
|
||||
return request.getRemoteAddr();
|
||||
|
||||
}
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Returns the credentials associated with the given request, using the
|
||||
@@ -205,228 +133,6 @@ public class TokenRESTService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts authentication against all AuthenticationProviders, in order,
|
||||
* using the provided credentials. The first authentication failure takes
|
||||
* priority, but remaining AuthenticationProviders are attempted. If any
|
||||
* AuthenticationProvider succeeds, the resulting AuthenticatedUser is
|
||||
* returned, and no further AuthenticationProviders are tried.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use for authentication.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticatedUser given by the highest-priority
|
||||
* AuthenticationProvider for which the given credentials are valid.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the given credentials are not valid for any
|
||||
* AuthenticationProvider, or if an error occurs while authenticating
|
||||
* the user.
|
||||
*/
|
||||
private AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleCredentialsException authFailure = null;
|
||||
|
||||
// Attempt authentication against each AuthenticationProvider
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Attempt authentication
|
||||
try {
|
||||
AuthenticatedUser authenticatedUser = authProvider.authenticateUser(credentials);
|
||||
if (authenticatedUser != null)
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
// First failure takes priority for now
|
||||
catch (GuacamoleCredentialsException e) {
|
||||
if (authFailure == null)
|
||||
authFailure = e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If a specific failure occured, rethrow that
|
||||
if (authFailure != null)
|
||||
throw authFailure;
|
||||
|
||||
// Otherwise, request standard username/password
|
||||
throw new GuacamoleInvalidCredentialsException(
|
||||
"Permission Denied.",
|
||||
CredentialsInfo.USERNAME_PASSWORD
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-authenticates the given AuthenticatedUser against the
|
||||
* AuthenticationProvider that originally created it, using the given
|
||||
* Credentials.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser to re-authenticate.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials to use to re-authenticate the user.
|
||||
*
|
||||
* @return
|
||||
* A AuthenticatedUser which may have been updated due to re-
|
||||
* authentication.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents the user from being re-authenticated.
|
||||
*/
|
||||
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
// Get original AuthenticationProvider
|
||||
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
||||
|
||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
if (authenticatedUser == null)
|
||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
||||
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthenticatedUser associated with the given session and
|
||||
* credentials, performing a fresh authentication and creating a new
|
||||
* AuthenticatedUser if necessary.
|
||||
*
|
||||
* @param existingSession
|
||||
* The current GuacamoleSession, or null if no session exists yet.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials to use to authenticate the user.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticatedUser associated with the given session and
|
||||
* credentials.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while authenticating or re-authenticating the
|
||||
* user.
|
||||
*/
|
||||
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Re-authenticate user if session exists
|
||||
if (existingSession != null)
|
||||
return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
||||
|
||||
// Otherwise, attempt authentication as a new user
|
||||
AuthenticatedUser authenticatedUser = authenticateUser(credentials);
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(),
|
||||
getLoggableAddress(credentials.getRequest()));
|
||||
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
// Log and rethrow any authentication errors
|
||||
catch (GuacamoleException e) {
|
||||
|
||||
// Get request and username for sake of logging
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
String username = credentials.getUsername();
|
||||
|
||||
// Log authentication failures with associated usernames
|
||||
if (username != null) {
|
||||
if (logger.isWarnEnabled())
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
||||
getLoggableAddress(request), username);
|
||||
}
|
||||
|
||||
// Log anonymous authentication failures
|
||||
else if (logger.isDebugEnabled())
|
||||
logger.debug("Anonymous authentication attempt from {} failed.",
|
||||
getLoggableAddress(request));
|
||||
|
||||
// Rethrow exception
|
||||
throw e;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with the given AuthenticatedUser,
|
||||
* updating existing UserContexts, if any. If no UserContexts are yet
|
||||
* associated with the given AuthenticatedUser, new UserContexts are
|
||||
* generated by polling each available AuthenticationProvider.
|
||||
*
|
||||
* @param existingSession
|
||||
* The current GuacamoleSession, or null if no session exists yet.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser that has successfully authenticated or re-
|
||||
* authenticated.
|
||||
*
|
||||
* @return
|
||||
* A List of all UserContexts associated with the given
|
||||
* AuthenticatedUser.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating or updating any UserContext.
|
||||
*/
|
||||
private List<UserContext> getUserContexts(GuacamoleSession existingSession,
|
||||
AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||
|
||||
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
|
||||
|
||||
// If UserContexts already exist, update them and add to the list
|
||||
if (existingSession != null) {
|
||||
|
||||
// Update all old user contexts
|
||||
List<UserContext> oldUserContexts = existingSession.getUserContexts();
|
||||
for (UserContext oldUserContext : oldUserContexts) {
|
||||
|
||||
// Update existing UserContext
|
||||
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
||||
UserContext userContext = authProvider.updateUserContext(oldUserContext, authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
// If unsuccessful, log that this happened, as it may be a bug
|
||||
else
|
||||
logger.debug("AuthenticationProvider \"{}\" retroactively destroyed its UserContext.",
|
||||
authProvider.getClass().getName());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, create new UserContexts from available AuthenticationProviders
|
||||
else {
|
||||
|
||||
// Get UserContexts from each available AuthenticationProvider
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Generate new UserContext
|
||||
UserContext userContext = authProvider.getUserContext(authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return userContexts;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user, generates an auth token, associates that auth token
|
||||
* with the user's UserContext for use by further requests. If an existing
|
||||
@@ -472,43 +178,27 @@ public class TokenRESTService {
|
||||
// Reconstitute the HTTP request with the map of parameters
|
||||
HttpServletRequest request = new APIRequest(consumedRequest, parameters);
|
||||
|
||||
// Pull existing session if token provided
|
||||
GuacamoleSession existingSession;
|
||||
if (token != null)
|
||||
existingSession = tokenSessionMap.get(token);
|
||||
else
|
||||
existingSession = null;
|
||||
|
||||
// Build credentials from request
|
||||
Credentials credentials = getCredentials(request, username, password);
|
||||
|
||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
||||
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
||||
List<UserContext> userContexts = getUserContexts(existingSession, authenticatedUser);
|
||||
// Create/update session producing possibly-new token
|
||||
token = authenticationService.authenticate(credentials, token);
|
||||
|
||||
// Update existing session, if it exists
|
||||
String authToken;
|
||||
if (existingSession != null) {
|
||||
authToken = token;
|
||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||
existingSession.setUserContexts(userContexts);
|
||||
}
|
||||
|
||||
// If no existing session, generate a new token/session pair
|
||||
else {
|
||||
authToken = authTokenGenerator.getToken();
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||
}
|
||||
// Pull corresponding session
|
||||
GuacamoleSession session = authenticationService.getGuacamoleSession(token);
|
||||
if (session == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||
|
||||
// Build list of all available auth providers
|
||||
List<UserContext> userContexts = session.getUserContexts();
|
||||
List<String> authProviderIdentifiers = new ArrayList<String>(userContexts.size());
|
||||
for (UserContext userContext : userContexts)
|
||||
authProviderIdentifiers.add(userContext.getAuthenticationProvider().getIdentifier());
|
||||
|
||||
// Return possibly-new auth token
|
||||
AuthenticatedUser authenticatedUser = session.getAuthenticatedUser();
|
||||
return new APIAuthenticationResult(
|
||||
authToken,
|
||||
token,
|
||||
authenticatedUser.getIdentifier(),
|
||||
authenticatedUser.getAuthenticationProvider().getIdentifier(),
|
||||
authProviderIdentifiers
|
||||
@@ -530,12 +220,10 @@ public class TokenRESTService {
|
||||
@Path("/{token}")
|
||||
public void invalidateToken(@PathParam("token") String authToken)
|
||||
throws GuacamoleException {
|
||||
|
||||
GuacamoleSession session = tokenSessionMap.remove(authToken);
|
||||
if (session == null)
|
||||
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||
|
||||
session.invalidate();
|
||||
// Invalidate session, if it exists
|
||||
if (!authenticationService.destroyGuacamoleSession(authToken))
|
||||
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user