diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java index 1b3e834b3..92bb63f06 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java @@ -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; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServiceModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServiceModule.java index c194495c5..40ee2d330 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServiceModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServiceModule.java @@ -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); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java index 7667bb43c..a4727686f 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/AuthenticationService.java @@ -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 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 getUserContexts(GuacamoleSession existingSession, + AuthenticatedUser authenticatedUser) throws GuacamoleException { + + List userContexts = new ArrayList(authProviders.size()); + + // If UserContexts already exist, update them and add to the list + if (existingSession != null) { + + // Update all old user contexts + List 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 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 diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java index 4351b890e..da64316c6 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java @@ -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 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 getUserContexts(GuacamoleSession existingSession, - AuthenticatedUser authenticatedUser) throws GuacamoleException { - - List userContexts = new ArrayList(authProviders.size()); - - // If UserContexts already exist, update them and add to the list - if (existingSession != null) { - - // Update all old user contexts - List 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 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 userContexts = session.getUserContexts(); List authProviderIdentifiers = new ArrayList(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."); }