mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUAC-586: Load multiple AuthenticationProviders.
This commit is contained in:
@@ -22,6 +22,8 @@
|
||||
|
||||
package org.glyptodon.guacamole.net.basic;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
@@ -51,9 +53,10 @@ public class GuacamoleSession {
|
||||
private AuthenticatedUser authenticatedUser;
|
||||
|
||||
/**
|
||||
* The user context associated with this session.
|
||||
* All UserContexts associated with this session. Each
|
||||
* AuthenticationProvider may provide its own UserContext.
|
||||
*/
|
||||
private UserContext userContext;
|
||||
private List<UserContext> userContexts;
|
||||
|
||||
/**
|
||||
* All currently-active tunnels, indexed by tunnel UUID.
|
||||
@@ -66,7 +69,8 @@ public class GuacamoleSession {
|
||||
private long lastAccessedTime;
|
||||
|
||||
/**
|
||||
* Creates a new Guacamole session associated with the given user context.
|
||||
* Creates a new Guacamole session associated with the given
|
||||
* AuthenticatedUser and UserContexts.
|
||||
*
|
||||
* @param environment
|
||||
* The environment of the Guacamole server associated with this new
|
||||
@@ -75,18 +79,19 @@ public class GuacamoleSession {
|
||||
* @param authenticatedUser
|
||||
* The authenticated user to associate this session with.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context to associate this session with.
|
||||
* @param userContexts
|
||||
* The List of UserContexts to associate with this session.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error prevents the session from being created.
|
||||
*/
|
||||
public GuacamoleSession(Environment environment,
|
||||
AuthenticatedUser authenticatedUser, UserContext userContext)
|
||||
AuthenticatedUser authenticatedUser,
|
||||
List<UserContext> userContexts)
|
||||
throws GuacamoleException {
|
||||
this.lastAccessedTime = System.currentTimeMillis();
|
||||
this.authenticatedUser = authenticatedUser;
|
||||
this.userContext = userContext;
|
||||
this.userContexts = userContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,18 +121,56 @@ public class GuacamoleSession {
|
||||
* @return The UserContext associated with this session.
|
||||
*/
|
||||
public UserContext getUserContext() {
|
||||
return userContext;
|
||||
|
||||
// Warn of deprecation
|
||||
logger.debug(
|
||||
"\n****************************************************************"
|
||||
+ "\n"
|
||||
+ "\n !!!! PLEASE DO NOT USE getUserContext() !!!!"
|
||||
+ "\n"
|
||||
+ "\n getUserContext() has been replaced by getUserContexts(), which"
|
||||
+ "\n properly handles multiple authentication providers. All use of"
|
||||
+ "\n the old getUserContext() must be removed before GUAC-586 can"
|
||||
+ "\n be considered complete."
|
||||
+ "\n"
|
||||
+ "\n****************************************************************"
|
||||
);
|
||||
|
||||
// Return the UserContext associated with the AuthenticationProvider
|
||||
// that authenticated the current user.
|
||||
String authProviderIdentifier = authenticatedUser.getAuthenticationProvider().getIdentifier();
|
||||
for (UserContext userContext : userContexts) {
|
||||
if (userContext.getAuthenticationProvider().getIdentifier().equals(authProviderIdentifier))
|
||||
return userContext;
|
||||
}
|
||||
|
||||
// If not found, return null
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the user context associated with this session with the given
|
||||
* user context.
|
||||
* Returns a list of all UserContexts associated with this session. Each
|
||||
* AuthenticationProvider currently loaded by Guacamole may provide its own
|
||||
* UserContext for any successfully-authenticated user.
|
||||
*
|
||||
* @param userContext
|
||||
* The user context to associate with this session.
|
||||
* @return
|
||||
* An unmodifiable list of all UserContexts associated with this
|
||||
* session.
|
||||
*/
|
||||
public void setUserContext(UserContext userContext) {
|
||||
this.userContext = userContext;
|
||||
public List<UserContext> getUserContexts() {
|
||||
return Collections.unmodifiableList(userContexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all UserContexts associated with this session with the given
|
||||
* List of UserContexts.
|
||||
*
|
||||
* @param userContexts
|
||||
* The List of UserContexts to associate with this session.
|
||||
*/
|
||||
public void setUserContexts(List<UserContext> userContexts) {
|
||||
this.userContexts = userContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -244,13 +244,13 @@ public class TunnelRequestService {
|
||||
// Connection identifiers
|
||||
case CONNECTION:
|
||||
logger.info("User \"{}\" disconnected from connection \"{}\". Duration: {} milliseconds",
|
||||
session.getUserContext().self().getIdentifier(), id, duration);
|
||||
session.getAuthenticatedUser().getIdentifier(), id, duration);
|
||||
break;
|
||||
|
||||
// Connection group identifiers
|
||||
case CONNECTION_GROUP:
|
||||
logger.info("User \"{}\" disconnected from connection group \"{}\". Duration: {} milliseconds",
|
||||
session.getUserContext().self().getIdentifier(), id, duration);
|
||||
session.getAuthenticatedUser().getIdentifier(), id, duration);
|
||||
break;
|
||||
|
||||
// Type is guaranteed to be one of the above
|
||||
|
@@ -22,6 +22,7 @@
|
||||
|
||||
package org.glyptodon.guacamole.net.basic.extension;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
@@ -91,10 +92,10 @@ public class ExtensionModule extends ServletModule {
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* The currently-bound authentication provider, if any. At the moment, we
|
||||
* only support one authentication provider loaded at any one time.
|
||||
* All currently-bound authentication providers, if any.
|
||||
*/
|
||||
private Class<? extends AuthenticationProvider> boundAuthenticationProvider = null;
|
||||
private final List<AuthenticationProvider> boundAuthenticationProviders =
|
||||
new ArrayList<AuthenticationProvider>();
|
||||
|
||||
/**
|
||||
* Service for adding and retrieving language resources.
|
||||
@@ -179,40 +180,24 @@ public class ExtensionModule extends ServletModule {
|
||||
/**
|
||||
* Binds the given AuthenticationProvider class such that any service
|
||||
* requiring access to the AuthenticationProvider can obtain it via
|
||||
* injection.
|
||||
* injection, along with any other bound AuthenticationProviders.
|
||||
*
|
||||
* @param authenticationProvider
|
||||
* The AuthenticationProvider class to bind.
|
||||
*/
|
||||
private void bindAuthenticationProvider(Class<? extends AuthenticationProvider> authenticationProvider) {
|
||||
|
||||
// Choose auth provider for binding if not already chosen
|
||||
if (boundAuthenticationProvider == null)
|
||||
boundAuthenticationProvider = authenticationProvider;
|
||||
|
||||
// If an auth provider is already chosen, skip and warn
|
||||
else {
|
||||
logger.debug("Ignoring AuthenticationProvider \"{}\".", authenticationProvider);
|
||||
logger.warn("Only one authentication extension may be used at a time. Please "
|
||||
+ "make sure that only one authentication extension is present "
|
||||
+ "within the GUACAMOLE_HOME/" + EXTENSIONS_DIRECTORY + " "
|
||||
+ "directory, and that you are not also specifying the deprecated "
|
||||
+ "\"auth-provider\" property within guacamole.properties.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind authentication provider
|
||||
logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider);
|
||||
bind(AuthenticationProvider.class).toInstance(new AuthenticationProviderFacade(authenticationProvider));
|
||||
logger.debug("[{}] Binding AuthenticationProvider \"{}\".",
|
||||
boundAuthenticationProviders.size(), authenticationProvider.getName());
|
||||
boundAuthenticationProviders.add(new AuthenticationProviderFacade(authenticationProvider));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds each of the the given AuthenticationProvider classes such that any
|
||||
* service requiring access to the AuthenticationProvider can obtain it via
|
||||
* injection. Note that, as multiple simultaneous authentication providers
|
||||
* are not currently supported, attempting to bind more than one
|
||||
* authentication provider will result in warnings being logged.
|
||||
* injection.
|
||||
*
|
||||
* @param authProviders
|
||||
* The AuthenticationProvider classes to bind.
|
||||
@@ -225,6 +210,18 @@ public class ExtensionModule extends ServletModule {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all currently-bound AuthenticationProvider instances.
|
||||
*
|
||||
* @return
|
||||
* A List of all currently-bound AuthenticationProvider. The List is
|
||||
* not modifiable.
|
||||
*/
|
||||
@Provides
|
||||
public List<AuthenticationProvider> getAuthenticationProviders() {
|
||||
return Collections.unmodifiableList(boundAuthenticationProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves each of the given resources as a language resource. Language
|
||||
* resources are served from within the "/translations" directory as JSON
|
||||
@@ -415,11 +412,8 @@ public class ExtensionModule extends ServletModule {
|
||||
// Load all extensions
|
||||
loadExtensions(javaScriptResources, cssResources);
|
||||
|
||||
// Bind basic auth if nothing else chosen/provided
|
||||
if (boundAuthenticationProvider == null) {
|
||||
logger.info("Using default, \"basic\", XML-driven authentication.");
|
||||
bindAuthenticationProvider(BasicFileAuthenticationProvider.class);
|
||||
}
|
||||
// Always bind basic auth last
|
||||
bindAuthenticationProvider(BasicFileAuthenticationProvider.class);
|
||||
|
||||
// Dynamically generate app.js and app.css from extensions
|
||||
serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources)));
|
||||
|
@@ -22,6 +22,7 @@
|
||||
|
||||
package org.glyptodon.guacamole.net.basic.rest;
|
||||
|
||||
import java.util.List;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.glyptodon.guacamole.net.auth.Connection;
|
||||
@@ -29,6 +30,7 @@ import org.glyptodon.guacamole.net.auth.ConnectionGroup;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
import org.glyptodon.guacamole.net.auth.User;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
|
||||
import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup;
|
||||
|
||||
/**
|
||||
@@ -39,6 +41,39 @@ import org.glyptodon.guacamole.net.basic.rest.connectiongroup.APIConnectionGroup
|
||||
*/
|
||||
public class ObjectRetrievalService {
|
||||
|
||||
/**
|
||||
* Retrieves a single UserContext from the given GuacamoleSession, which
|
||||
* may contain multiple UserContexts.
|
||||
*
|
||||
* @param session
|
||||
* The GuacamoleSession to retrieve the UserContext from.
|
||||
*
|
||||
* @param id
|
||||
* The numeric ID of the UserContext to retrieve. This ID is the index
|
||||
* of the UserContext within the overall list of UserContexts
|
||||
* associated with the user's session.
|
||||
*
|
||||
* @return
|
||||
* The user having the given identifier.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the user, or if the
|
||||
* user does not exist.
|
||||
*/
|
||||
public UserContext retrieveUserContext(GuacamoleSession session,
|
||||
int id) throws GuacamoleException {
|
||||
|
||||
// Get list of UserContexts
|
||||
List<UserContext> userContexts = session.getUserContexts();
|
||||
|
||||
// Verify context exists
|
||||
if (id < 0 || id >= userContexts.size())
|
||||
throw new GuacamoleResourceNotFoundException("No such user context: \"" + id + "\"");
|
||||
|
||||
return userContexts.get(id);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single user from the given user context.
|
||||
*
|
||||
|
@@ -23,6 +23,7 @@
|
||||
package org.glyptodon.guacamole.net.basic.rest.auth;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.List;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
@@ -77,5 +78,24 @@ public class AuthenticationService {
|
||||
public UserContext getUserContext(String authToken) throws GuacamoleException {
|
||||
return getGuacamoleSession(authToken).getUserContext();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all UserContexts associated with 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
|
||||
* A List of all UserContexts associated with the provided auth token.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the auth token does not correspond to any logged in user.
|
||||
*/
|
||||
public List<UserContext> getUserContexts(String authToken)
|
||||
throws GuacamoleException {
|
||||
return getGuacamoleSession(authToken).getUserContexts();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,6 +24,8 @@ package org.glyptodon.guacamole.net.basic.rest.auth;
|
||||
|
||||
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;
|
||||
@@ -68,10 +70,11 @@ public class TokenRESTService {
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* The authentication provider used to authenticate this user.
|
||||
* All configured authentication providers which can be used to
|
||||
* authenticate users or retrieve data associated with authenticated users.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationProvider authProvider;
|
||||
private List<AuthenticationProvider> authProviders;
|
||||
|
||||
/**
|
||||
* The map of auth tokens to sessions for the REST endpoints.
|
||||
@@ -224,23 +227,31 @@ public class TokenRESTService {
|
||||
credentials.setRequest(request);
|
||||
credentials.setSession(request.getSession(true));
|
||||
|
||||
AuthenticatedUser authenticatedUser;
|
||||
AuthenticatedUser authenticatedUser = null;
|
||||
try {
|
||||
|
||||
// Re-authenticate user if session exists
|
||||
if (existingSession != null)
|
||||
authenticatedUser = authProvider.updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
||||
if (existingSession != null) {
|
||||
authenticatedUser = existingSession.getAuthenticatedUser();
|
||||
authenticatedUser = authenticatedUser.getAuthenticationProvider().updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
}
|
||||
|
||||
/// Otherwise, authenticate as a new user
|
||||
// Otherwise, attempt authentication as a new user against each
|
||||
// AuthenticationProvider, in deterministic order
|
||||
else {
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
authenticatedUser = authProvider.authenticateUser(credentials);
|
||||
// Attempt authentication
|
||||
authenticatedUser = authProvider.authenticateUser(credentials);
|
||||
|
||||
// Log successful authentication
|
||||
if (authenticatedUser != null && logger.isInfoEnabled())
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(), getLoggableAddress(request));
|
||||
// Stop after successful authentication
|
||||
if (authenticatedUser != null && logger.isInfoEnabled()) {
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(), getLoggableAddress(request));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Request standard username/password if no user was produced
|
||||
@@ -266,30 +277,35 @@ public class TokenRESTService {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Generate or update user context
|
||||
UserContext userContext;
|
||||
if (existingSession != null)
|
||||
userContext = authProvider.updateUserContext(existingSession.getUserContext(), authenticatedUser);
|
||||
else
|
||||
userContext = authProvider.getUserContext(authenticatedUser);
|
||||
// Get UserContexts from each available AuthenticationProvider
|
||||
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// STUB: Request standard username/password if no user context was produced
|
||||
if (userContext == null)
|
||||
throw new GuacamoleInvalidCredentialsException("Permission Denied.",
|
||||
CredentialsInfo.USERNAME_PASSWORD);
|
||||
// Generate or update user context
|
||||
UserContext userContext;
|
||||
if (existingSession != null)
|
||||
userContext = authProvider.updateUserContext(existingSession.getUserContext(), authenticatedUser);
|
||||
else
|
||||
userContext = authProvider.getUserContext(authenticatedUser);
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
userContexts.add(userContext);
|
||||
|
||||
}
|
||||
|
||||
// Update existing session, if it exists
|
||||
String authToken;
|
||||
if (existingSession != null) {
|
||||
authToken = token;
|
||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||
existingSession.setUserContext(userContext);
|
||||
existingSession.setUserContexts(userContexts);
|
||||
}
|
||||
|
||||
// If no existing session, generate a new token/session pair
|
||||
else {
|
||||
authToken = authTokenGenerator.getToken();
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContext));
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||
}
|
||||
|
||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||
|
@@ -41,7 +41,6 @@ import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
import org.glyptodon.guacamole.net.auth.User;
|
||||
@@ -122,12 +121,6 @@ public class UserRESTService {
|
||||
@Inject
|
||||
private ObjectRetrievalService retrievalService;
|
||||
|
||||
/**
|
||||
* The authentication provider used to authenticating a user.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* Gets a list of users in the system, filtering the returned list by the
|
||||
* given permission, if specified.
|
||||
@@ -340,7 +333,7 @@ public class UserRESTService {
|
||||
|
||||
// Verify that the old password was correct
|
||||
try {
|
||||
if (authProvider.authenticateUser(credentials) == null) {
|
||||
if (userContext.getAuthenticationProvider().authenticateUser(credentials) == null) {
|
||||
throw new APIException(APIError.Type.PERMISSION_DENIED,
|
||||
"Permission denied.");
|
||||
}
|
||||
|
Reference in New Issue
Block a user