diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java index bc7580033..0ffc7edd1 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/JDBCAuthenticationProviderModule.java @@ -63,6 +63,7 @@ import org.glyptodon.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermis import org.glyptodon.guacamole.auth.jdbc.activeconnection.ActiveConnectionService; import org.glyptodon.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection; import org.glyptodon.guacamole.environment.Environment; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.mybatis.guice.MyBatisModule; import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider; @@ -86,19 +87,31 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { */ private final GuacamoleTunnelService tunnelService; + /** + * The AuthenticationProvider which is using this module to configure + * injection. + */ + private final AuthenticationProvider authProvider; + /** * Creates a new JDBC authentication provider module that configures the * various injected base classes using the given environment, and provides * connections using the given socket service. * + * @param authProvider + * The AuthenticationProvider which is using this module to configure + * injection. + * * @param environment * The environment to use to configure injected classes. * * @param tunnelService * The tunnel service to use to provide tunnels sockets for connections. */ - public JDBCAuthenticationProviderModule(Environment environment, + public JDBCAuthenticationProviderModule(AuthenticationProvider authProvider, + Environment environment, GuacamoleTunnelService tunnelService) { + this.authProvider = authProvider; this.environment = environment; this.tunnelService = tunnelService; } @@ -126,6 +139,7 @@ public class JDBCAuthenticationProviderModule extends MyBatisModule { // Bind core implementations of guacamole-ext classes bind(ActiveConnectionDirectory.class); bind(ActiveConnectionPermissionSet.class); + bind(AuthenticationProvider.class).toInstance(authProvider); bind(Environment.class).toInstance(environment); bind(ConnectionDirectory.class); bind(ConnectionGroupDirectory.class); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticatedUser.java index 480000e62..8e8747575 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticatedUser.java @@ -25,6 +25,7 @@ package org.glyptodon.guacamole.auth.jdbc.user; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Credentials; /** @@ -32,7 +33,7 @@ import org.glyptodon.guacamole.net.auth.Credentials; * * @author Michael Jumper */ -public class AuthenticatedUser { +public class AuthenticatedUser implements org.glyptodon.guacamole.net.auth.AuthenticatedUser { /** * The user that authenticated. @@ -44,6 +45,11 @@ public class AuthenticatedUser { */ private final Credentials credentials; + /** + * The AuthenticationProvider that authenticated this user. + */ + private final AuthenticationProvider authenticationProvider; + /** * The host from which this user authenticated. */ @@ -106,13 +112,18 @@ public class AuthenticatedUser { * Creates a new AuthenticatedUser associating the given user with their * corresponding credentials. * + * @param authenticationProvider + * The AuthenticationProvider that has authenticated the given user. + * * @param user * The user this object should represent. * * @param credentials * The credentials given by the user when they authenticated. */ - public AuthenticatedUser(ModeledUser user, Credentials credentials) { + public AuthenticatedUser(AuthenticationProvider authenticationProvider, + ModeledUser user, Credentials credentials) { + this.authenticationProvider = authenticationProvider; this.user = user; this.credentials = credentials; this.remoteHost = getRemoteHost(credentials); @@ -134,6 +145,7 @@ public class AuthenticatedUser { * @return * The credentials given during authentication by this user. */ + @Override public Credentials getCredentials() { return credentials; } @@ -148,4 +160,19 @@ public class AuthenticatedUser { return remoteHost; } + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authenticationProvider; + } + + @Override + public String getIdentifier() { + return user.getIdentifier(); + } + + @Override + public void setIdentifier(String identifier) { + user.setIdentifier(identifier); + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticationProviderService.java similarity index 57% rename from extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java rename to extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticationProviderService.java index b980b10ef..486e96242 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContextService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/AuthenticationProviderService.java @@ -25,17 +25,19 @@ package org.glyptodon.guacamole.auth.jdbc.user; import com.google.inject.Inject; import com.google.inject.Provider; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; /** - * Service which creates new UserContext instances for valid users based on - * credentials. + * Service which authenticates users based on credentials and provides for + * the creation of corresponding, new UserContext objects for authenticated + * users. * * @author Michael Jumper */ -public class UserContextService { +public class AuthenticationProviderService { /** * Service for accessing users. @@ -51,11 +53,44 @@ public class UserContextService { /** * Authenticates the user having the given credentials, returning a new - * UserContext instance only if the credentials are valid. If the + * AuthenticatedUser instance only if the credentials are valid. If the * credentials are invalid or expired, an appropriate GuacamoleException * will be thrown. * + * @param authenticationProvider + * The AuthenticationProvider on behalf of which the user is being + * authenticated. + * * @param credentials + * The credentials to use to produce the AuthenticatedUser. + * + * @return + * A new AuthenticatedUser instance for the user identified by the + * given credentials. + * + * @throws GuacamoleException + * If an error occurs during authentication, or if the given + * credentials are invalid or expired. + */ + public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider, + Credentials credentials) throws GuacamoleException { + + // Authenticate user + AuthenticatedUser user = userService.retrieveAuthenticatedUser(authenticationProvider, credentials); + if (user != null) + return user; + + // Otherwise, unauthorized + throw new GuacamoleInvalidCredentialsException("Invalid login", CredentialsInfo.USERNAME_PASSWORD); + + } + + /** + * Returning a new UserContext instance for the given already-authenticated + * user. A new placeholder account will be created for any user that does + * not already exist within the database. + * + * @param authenticatedUser * The credentials to use to produce the UserContext. * * @return @@ -66,23 +101,18 @@ public class UserContextService { * If an error occurs during authentication, or if the given * credentials are invalid or expired. */ - public org.glyptodon.guacamole.net.auth.UserContext - getUserContext(Credentials credentials) + public UserContext getUserContext(org.glyptodon.guacamole.net.auth.AuthenticatedUser authenticatedUser) throws GuacamoleException { - // Authenticate user - ModeledUser user = userService.retrieveUser(credentials); - if (user != null) { + // Retrieve user account for already-authenticated user + ModeledUser user = userService.retrieveUser(authenticatedUser); + if (user == null) + return null; - // Upon successful authentication, return new user context - UserContext context = userContextProvider.get(); - context.init(user.getCurrentUser()); - return context; - - } - - // Otherwise, unauthorized - throw new GuacamoleInvalidCredentialsException("Invalid login", CredentialsInfo.USERNAME_PASSWORD); + // Link to user context + UserContext context = userContextProvider.get(); + context.init(user.getCurrentUser()); + return context; } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java index 12676927a..9ac828bce 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserContext.java @@ -36,6 +36,7 @@ import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; import org.glyptodon.guacamole.form.Form; import org.glyptodon.guacamole.net.auth.ActiveConnection; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.Directory; @@ -51,6 +52,12 @@ import org.glyptodon.guacamole.net.auth.User; public class UserContext extends RestrictedObject implements org.glyptodon.guacamole.net.auth.UserContext { + /** + * The AuthenticationProvider that created this UserContext. + */ + @Inject + private AuthenticationProvider authProvider; + /** * User directory restricted by the permissions of the user associated * with this context. @@ -103,6 +110,11 @@ public class UserContext extends RestrictedObject return getCurrentUser().getUser(); } + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + @Override public Directory getUserDirectory() throws GuacamoleException { return userDirectory; diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java index 1a9e6090a..a2f38f133 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/user/UserService.java @@ -40,6 +40,7 @@ import org.glyptodon.guacamole.auth.jdbc.permission.UserPermissionMapper; import org.glyptodon.guacamole.auth.jdbc.security.PasswordEncryptionService; import org.glyptodon.guacamole.form.Field; import org.glyptodon.guacamole.form.PasswordField; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.User; import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; @@ -265,18 +266,22 @@ public class UserService extends ModeledDirectoryObjectService readProtocols() throws GuacamoleException { + private Map readProtocols() { // Map of all available protocols Map protocols = new HashMap(); diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AbstractAuthenticatedUser.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AbstractAuthenticatedUser.java new file mode 100644 index 000000000..93256d3cf --- /dev/null +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AbstractAuthenticatedUser.java @@ -0,0 +1,73 @@ +/* + * 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.auth; + + +/** + * Basic implementation of an AuthenticatedUser which uses the username to + * determine equality. Username comparison is case-sensitive. + * + * @author Michael Jumper + */ +public abstract class AbstractAuthenticatedUser implements AuthenticatedUser { + + /** + * The name of this user. + */ + private String username; + + @Override + public String getIdentifier() { + return username; + } + + @Override + public void setIdentifier(String username) { + this.username = username; + } + + @Override + public int hashCode() { + if (username == null) return 0; + return username.hashCode(); + } + + @Override + public boolean equals(Object obj) { + + // Not equal if null or not a User + if (obj == null) return false; + if (!(obj instanceof AbstractAuthenticatedUser)) return false; + + // Get username + String objUsername = ((AbstractAuthenticatedUser) obj).username; + + // If null, equal only if this username is null + if (objUsername == null) return username == null; + + // Otherwise, equal only if strings are identical + return objUsername.equals(username); + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticatedUser.java similarity index 53% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java rename to guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticatedUser.java index 5d71c9edb..1073d47d3 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthToken.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticatedUser.java @@ -1,16 +1,16 @@ /* - * 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 * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,50 +20,32 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.rest.auth; +package org.glyptodon.guacamole.net.auth; + /** - * A simple object to represent an auth token/userID pair in the API. - * - * @author James Muehlner + * A user of the Guacamole web application who has been authenticated by an + * AuthenticationProvider. + * + * @author Michael Jumper */ -public class APIAuthToken { - - /** - * The auth token. - */ - private final String authToken; - - - /** - * The user ID. - */ - private final String userID; +public interface AuthenticatedUser extends Identifiable { /** - * Get the auth token. - * @return The auth token. + * Returns the AuthenticationProvider that authenticated this user. + * + * @return + * The AuthenticationProvider that authenticated this user. */ - public String getAuthToken() { - return authToken; - } - + AuthenticationProvider getAuthenticationProvider(); + /** - * Get the user ID. - * @return The user ID. + * Returns the credentials that the user provided when they successfully + * authenticated. + * + * @return + * The credentials provided by the user when they authenticated. */ - public String getUserID() { - return userID; - } - - /** - * Create a new APIAuthToken Object with the given auth token. - * - * @param authToken The auth token to create the new APIAuthToken with. - * @param userID The ID of the user owning the given token. - */ - public APIAuthToken(String authToken, String userID) { - this.authToken = authToken; - this.userID = userID; - } + Credentials getCredentials(); + } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticationProvider.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticationProvider.java index d843fd232..077edbd5a 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticationProvider.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/AuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 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 @@ -25,46 +25,124 @@ package org.glyptodon.guacamole.net.auth; import org.glyptodon.guacamole.GuacamoleException; /** - * Provides means of accessing and managing the available - * GuacamoleConfiguration objects and User objects. Access to each configuration - * and each user is limited by a given Credentials object. + * Provides means of authorizing users and for accessing and managing data + * associated with those users. Access to such data is limited according to the + * AuthenticationProvider implementation. * * @author Michael Jumper */ public interface AuthenticationProvider { /** - * Returns the UserContext of the user authorized by the given credentials. + * Returns the identifier which uniquely and consistently identifies this + * AuthenticationProvider implementation. This identifier may not be null + * and must be unique across all AuthenticationProviders loaded by the + * Guacamole web application. * - * @param credentials The credentials to use to retrieve the environment. - * @return The UserContext of the user authorized by the given credentials, - * or null if the credentials are not authorized. - * - * @throws GuacamoleException If an error occurs while creating the - * UserContext. + * @return + * The unique identifier assigned to this AuthenticationProvider, which + * may not be null. */ - UserContext getUserContext(Credentials credentials) + String getIdentifier(); + + /** + * Returns an AuthenticatedUser representing the user authenticated by the + * given credentials, if any. + * + * @param credentials + * The credentials to use for authentication. + * + * @return + * An AuthenticatedUser representing the user authenticated by the + * given credentials, if any, or null if the credentials are invalid. + * + * @throws GuacamoleException + * If an error occurs while authenticating the user, or if access is + * temporarily, permanently, or conditionally denied, such as if the + * supplied credentials are insufficient or invalid. + */ + AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException; /** - * Returns a new or updated UserContext for the user authorized by the - * give credentials and having the given existing UserContext. Note that - * because this function will be called for all future requests after - * initial authentication, including tunnel requests, care must be taken - * to avoid using functions of HttpServletRequest which invalidate the - * entire request body, such as getParameter(). - * - * @param context The existing UserContext belonging to the user in - * question. - * @param credentials The credentials to use to retrieve or update the - * environment. - * @return The updated UserContext, which need not be the same as the - * UserContext given, or null if the user is no longer authorized. - * - * @throws GuacamoleException If an error occurs while updating the - * UserContext. + * Returns a new or updated AuthenticatedUser for the given credentials + * already having produced the given AuthenticatedUser. Note that because + * this function will be called for all future requests after initial + * authentication, including tunnel requests, care must be taken to avoid + * using functions of HttpServletRequest which invalidate the entire request + * body, such as getParameter(). Doing otherwise may cause the + * GuacamoleHTTPTunnelServlet to fail. + * + * @param credentials + * The credentials to use for authentication. + * + * @param authenticatedUser + * An AuthenticatedUser object representing the user authenticated by + * an arbitrary set of credentials. The AuthenticatedUser may come from + * this AuthenticationProvider or any other installed + * AuthenticationProvider. + * + * @return + * An updated AuthenticatedUser representing the user authenticated by + * the given credentials, if any, or null if the credentials are + * invalid. + * + * @throws GuacamoleException + * If an error occurs while updating the AuthenticatedUser. */ - UserContext updateUserContext(UserContext context, Credentials credentials) + AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser, + Credentials credentials) throws GuacamoleException; + + /** + * Returns the UserContext of the user authenticated by the given + * credentials. + * + * @param authenticatedUser + * An AuthenticatedUser object representing the user authenticated by + * an arbitrary set of credentials. The AuthenticatedUser may come from + * this AuthenticationProvider or any other installed + * AuthenticationProvider. + * + * @return + * A UserContext describing the permissions, connection, connection + * groups, etc. accessible or associated with the given authenticated + * user, or null if this AuthenticationProvider refuses to provide any + * such data. + * + * @throws GuacamoleException + * If an error occurs while creating the UserContext. + */ + UserContext getUserContext(AuthenticatedUser authenticatedUser) throws GuacamoleException; + + /** + * Returns a new or updated UserContext for the given AuthenticatedUser + * already having the given UserContext. Note that because this function + * will be called for all future requests after initial authentication, + * including tunnel requests, care must be taken to avoid using functions + * of HttpServletRequest which invalidate the entire request body, such as + * getParameter(). Doing otherwise may cause the GuacamoleHTTPTunnelServlet + * to fail. + * + * @param context + * The existing UserContext belonging to the user in question. + * + * @param authenticatedUser + * An AuthenticatedUser object representing the user authenticated by + * an arbitrary set of credentials. The AuthenticatedUser may come from + * this AuthenticationProvider or any other installed + * AuthenticationProvider. + * + * @return + * An updated UserContext describing the permissions, connection, + * connection groups, etc. accessible or associated with the given + * authenticated user, or null if this AuthenticationProvider refuses + * to provide any such data. + * + * @throws GuacamoleException + * If an error occurs while updating the UserContext. + */ + UserContext updateUserContext(UserContext context, + AuthenticatedUser authenticatedUser) throws GuacamoleException; } diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/UserContext.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/UserContext.java index e1dddb23c..f668a3586 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/UserContext.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/UserContext.java @@ -43,6 +43,16 @@ public interface UserContext { */ User self(); + /** + * Returns the AuthenticationProvider which created this UserContext, which + * may not be the same AuthenticationProvider that authenticated the user + * associated with this UserContext. + * + * @return + * The AuthenticationProvider that created this UserContext. + */ + AuthenticationProvider getAuthenticationProvider(); + /** * Retrieves a Directory which can be used to view and manipulate other * users, but only as allowed by the permissions given to the user of this diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleAuthenticationProvider.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleAuthenticationProvider.java index f48ffaaba..efc804c14 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleAuthenticationProvider.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleAuthenticationProvider.java @@ -23,8 +23,11 @@ package org.glyptodon.guacamole.net.auth.simple; import java.util.Map; +import java.util.UUID; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.AbstractAuthenticatedUser; import org.glyptodon.guacamole.net.auth.AuthenticationProvider; +import org.glyptodon.guacamole.net.auth.AuthenticatedUser; import org.glyptodon.guacamole.net.auth.Credentials; import org.glyptodon.guacamole.net.auth.UserContext; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; @@ -62,12 +65,100 @@ public abstract class SimpleAuthenticationProvider getAuthorizedConfigurations(Credentials credentials) throws GuacamoleException; - @Override - public UserContext getUserContext(Credentials credentials) - throws GuacamoleException { + /** + * AuthenticatedUser which contains its own predefined set of authorized + * configurations. + * + * @author Michael Jumper + */ + private class SimpleAuthenticatedUser extends AbstractAuthenticatedUser { - // Get username, if any - String username = credentials.getUsername(); + /** + * The credentials provided when this AuthenticatedUser was + * authenticated. + */ + private final Credentials credentials; + + /** + * The GuacamoleConfigurations that this AuthenticatedUser is + * authorized to use. + */ + private final Map configs; + + /** + * Creates a new SimpleAuthenticatedUser associated with the given + * credentials and having access to the given Map of + * GuacamoleConfigurations. + * + * @param credentials + * The credentials provided by the user when they authenticated. + * + * @param configs + * A Map of all GuacamoleConfigurations for which this user has + * access. The keys of this Map are Strings which uniquely identify + * each configuration. + */ + public SimpleAuthenticatedUser(Credentials credentials, Map configs) { + + // Store credentials and configurations + this.credentials = credentials; + this.configs = configs; + + // Pull username from credentials if it exists + String username = credentials.getUsername(); + if (username != null && !username.isEmpty()) + setIdentifier(username); + + // Otherwise generate a random username + else + setIdentifier(UUID.randomUUID().toString()); + + } + + /** + * Returns a Map containing all GuacamoleConfigurations that this user + * is authorized to use. The keys of this Map are Strings which + * uniquely identify each configuration. + * + * @return + * A Map of all configurations for which this user is authorized. + */ + public Map getAuthorizedConfigurations() { + return configs; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return SimpleAuthenticationProvider.this; + } + + @Override + public Credentials getCredentials() { + return credentials; + } + + } + + /** + * Given an arbitrary credentials object, returns a Map containing all + * configurations authorized by those credentials, filtering those + * configurations using a TokenFilter and the standard credential tokens + * (like ${GUAC_USERNAME} and ${GUAC_PASSWORD}). The keys of this Map + * are Strings which uniquely identify each configuration. + * + * @param credentials + * The credentials to use to retrieve authorized configurations. + * + * @return + * A Map of all configurations authorized by the given credentials, or + * null if the credentials given are not authorized. + * + * @throws GuacamoleException + * If an error occurs while retrieving configurations. + */ + private Map + getFilteredAuthorizedConfigurations(Credentials credentials) + throws GuacamoleException { // Get configurations Map configs = @@ -80,24 +171,90 @@ public abstract class SimpleAuthenticationProvider // Build credential TokenFilter TokenFilter tokenFilter = new TokenFilter(); StandardTokens.addStandardTokens(tokenFilter, credentials); - + // Filter each configuration for (GuacamoleConfiguration config : configs.values()) tokenFilter.filterValues(config.getParameters()); - - // Return user context restricted to authorized configs - if (username != null && !username.isEmpty()) - return new SimpleUserContext(username, configs); - // If there is no associated username, let SimpleUserContext generate one - else - return new SimpleUserContext(configs); + return configs; + + } + + /** + * Given a user who has already been authenticated, returns a Map + * containing all configurations for which that user is authorized, + * filtering those configurations using a TokenFilter and the standard + * credential tokens (like ${GUAC_USERNAME} and ${GUAC_PASSWORD}). The keys + * of this Map are Strings which uniquely identify each configuration. + * + * @param authenticatedUser + * The user whose authorized configurations are to be retrieved. + * + * @return + * A Map of all configurations authorized for use by the given user, or + * null if the user is not authorized to use any configurations. + * + * @throws GuacamoleException + * If an error occurs while retrieving configurations. + */ + private Map + getFilteredAuthorizedConfigurations(AuthenticatedUser authenticatedUser) + throws GuacamoleException { + + // Pull cached configurations, if any + if (authenticatedUser instanceof SimpleAuthenticatedUser) + return ((SimpleAuthenticatedUser) authenticatedUser).getAuthorizedConfigurations(); + + // Otherwise, pull using credentials + return getFilteredAuthorizedConfigurations(authenticatedUser.getCredentials()); + + } + + @Override + public AuthenticatedUser authenticateUser(final Credentials credentials) + throws GuacamoleException { + + // Get configurations + Map configs = + getFilteredAuthorizedConfigurations(credentials); + + // Return as unauthorized if not authorized to retrieve configs + if (configs == null) + return null; + + return new SimpleAuthenticatedUser(credentials, configs); + + } + + @Override + public UserContext getUserContext(AuthenticatedUser authenticatedUser) + throws GuacamoleException { + + // Get configurations + Map configs = + getFilteredAuthorizedConfigurations(authenticatedUser); + + // Return as unauthorized if not authorized to retrieve configs + if (configs == null) + return null; + + // Return user context restricted to authorized configs + return new SimpleUserContext(this, authenticatedUser.getIdentifier(), configs); + + } + + @Override + public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser, + Credentials credentials) throws GuacamoleException { + + // Simply return the given user, updating nothing + return authenticatedUser; } @Override public UserContext updateUserContext(UserContext context, - Credentials credentials) throws GuacamoleException { + AuthenticatedUser authorizedUser) throws GuacamoleException { // Simply return the given context, updating nothing return context; diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUserContext.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUserContext.java index 5a41e44d2..9293d46b3 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUserContext.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUserContext.java @@ -30,6 +30,7 @@ import java.util.UUID; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.form.Form; import org.glyptodon.guacamole.net.auth.ActiveConnection; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Connection; import org.glyptodon.guacamole.net.auth.ConnectionGroup; import org.glyptodon.guacamole.net.auth.Directory; @@ -50,7 +51,12 @@ public class SimpleUserContext implements UserContext { * The unique identifier of the root connection group. */ private static final String ROOT_IDENTIFIER = "ROOT"; - + + /** + * The AuthenticationProvider that created this UserContext. + */ + private final AuthenticationProvider authProvider; + /** * Reference to the user whose permissions dictate the configurations * accessible within this UserContext. @@ -84,24 +90,35 @@ public class SimpleUserContext implements UserContext { * Creates a new SimpleUserContext which provides access to only those * configurations within the given Map. The username is assigned * arbitrarily. - * - * @param configs A Map of all configurations for which the user associated - * with this UserContext has read access. + * + * @param authProvider + * The AuthenticationProvider creating this UserContext. + * + * @param configs + * A Map of all configurations for which the user associated with this + * UserContext has read access. */ - public SimpleUserContext(Map configs) { - this(UUID.randomUUID().toString(), configs); + public SimpleUserContext(AuthenticationProvider authProvider, + Map configs) { + this(authProvider, UUID.randomUUID().toString(), configs); } /** * Creates a new SimpleUserContext for the user with the given username * which provides access to only those configurations within the given Map. - * - * @param username The username of the user associated with this - * UserContext. - * @param configs A Map of all configurations for which the user associated - * with this UserContext has read access. + * + * @param authProvider + * The AuthenticationProvider creating this UserContext. + * + * @param username + * The username of the user associated with this UserContext. + * + * @param configs + * A Map of all configurations for which the user associated with + * this UserContext has read access. */ - public SimpleUserContext(String username, Map configs) { + public SimpleUserContext(AuthenticationProvider authProvider, + String username, Map configs) { Collection connectionIdentifiers = new ArrayList(configs.size()); Collection connectionGroupIdentifiers = Collections.singleton(ROOT_IDENTIFIER); @@ -138,7 +155,10 @@ public class SimpleUserContext implements UserContext { this.userDirectory = new SimpleUserDirectory(self); this.connectionDirectory = new SimpleConnectionDirectory(connections); this.connectionGroupDirectory = new SimpleConnectionGroupDirectory(Collections.singleton(this.rootGroup)); - + + // Associate provided AuthenticationProvider + this.authProvider = authProvider; + } @Override @@ -146,6 +166,11 @@ public class SimpleUserContext implements UserContext { return self; } + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + @Override public Directory getUserDirectory() throws GuacamoleException { diff --git a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java index fd7ffda9f..e868a9117 100644 --- a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java +++ b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/BasicFileAuthenticationProvider.java @@ -36,7 +36,7 @@ import org.glyptodon.guacamole.net.auth.simple.SimpleAuthenticationProvider; import org.glyptodon.guacamole.net.basic.auth.Authorization; import org.glyptodon.guacamole.net.basic.auth.UserMapping; import org.glyptodon.guacamole.xml.DocumentHandler; -import org.glyptodon.guacamole.net.basic.xml.user_mapping.UserMappingTagHandler; +import org.glyptodon.guacamole.net.basic.xml.usermapping.UserMappingTagHandler; import org.glyptodon.guacamole.properties.FileGuacamoleProperty; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; @@ -58,17 +58,19 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide /** * Logger for this class. */ - private Logger logger = LoggerFactory.getLogger(BasicFileAuthenticationProvider.class); + private final Logger logger = LoggerFactory.getLogger(BasicFileAuthenticationProvider.class); /** - * The time the user mapping file was last modified. + * The time the user mapping file was last modified. If the file has never + * been read, and thus no modification time exists, this will be + * Long.MIN_VALUE. */ - private long mod_time; + private long lastModified = Long.MIN_VALUE; /** * The parsed UserMapping read when the user mapping file was last parsed. */ - private UserMapping user_mapping; + private UserMapping cachedUserMapping; /** * Guacamole server environment. @@ -103,30 +105,48 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide environment = new LocalEnvironment(); } + @Override + public String getIdentifier() { + return "default"; + } + /** * Returns a UserMapping containing all authorization data given within * the XML file specified by the "basic-user-mapping" property in * guacamole.properties. If the XML file has been modified or has not yet * been read, this function may reread the file. * - * @return A UserMapping containing all authorization data within the - * user mapping XML file. - * @throws GuacamoleException If the user mapping property is missing or - * an error occurs while parsing the XML file. + * @return + * A UserMapping containing all authorization data within the user + * mapping XML file, or null if the file cannot be found/parsed. */ - private UserMapping getUserMapping() throws GuacamoleException { + private UserMapping getUserMapping() { // Get user mapping file, defaulting to GUACAMOLE_HOME/user-mapping.xml - File user_mapping_file = environment.getProperty(BASIC_USER_MAPPING); - if (user_mapping_file == null) - user_mapping_file = new File(environment.getGuacamoleHome(), DEFAULT_USER_MAPPING); + File userMappingFile; + try { + userMappingFile = environment.getProperty(BASIC_USER_MAPPING); + if (userMappingFile == null) + userMappingFile = new File(environment.getGuacamoleHome(), DEFAULT_USER_MAPPING); + } - // If user_mapping not yet read, or user_mapping has been modified, reread - if (user_mapping == null || - (user_mapping_file.exists() - && mod_time < user_mapping_file.lastModified())) { + // Abort if property cannot be parsed + catch (GuacamoleException e) { + logger.warn("Unable to read user mapping filename from properties: {}", e.getMessage()); + logger.debug("Error parsing user mapping property.", e); + return null; + } - logger.debug("Reading user mapping file: \"{}\"", user_mapping_file); + // Abort if user mapping does not exist + if (!userMappingFile.exists()) { + logger.debug("User mapping file \"{}\" does not exist and will not be read.", userMappingFile); + return null; + } + + // Refresh user mapping if file has changed + if (lastModified < userMappingFile.lastModified()) { + + logger.debug("Reading user mapping file: \"{}\"", userMappingFile); // Parse document try { @@ -144,26 +164,34 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide parser.setContentHandler(contentHandler); // Read and parse file - InputStream input = new BufferedInputStream(new FileInputStream(user_mapping_file)); + InputStream input = new BufferedInputStream(new FileInputStream(userMappingFile)); parser.parse(new InputSource(input)); input.close(); // Store mod time and user mapping - mod_time = user_mapping_file.lastModified(); - user_mapping = userMappingHandler.asUserMapping(); + lastModified = userMappingFile.lastModified(); + cachedUserMapping = userMappingHandler.asUserMapping(); } + + // If the file is unreadable, return no mapping catch (IOException e) { - throw new GuacamoleException("Error reading basic user mapping file.", e); + logger.warn("Unable to read user mapping file \"{}\": {}", userMappingFile, e.getMessage()); + logger.debug("Error reading user mapping file.", e); + return null; } + + // If the file cannot be parsed, return no mapping catch (SAXException e) { - throw new GuacamoleException("Error parsing basic user mapping XML.", e); + logger.warn("User mapping file \"{}\" is not valid: {}", userMappingFile, e.getMessage()); + logger.debug("Error parsing user mapping file.", e); + return null; } } // Return (possibly cached) user mapping - return user_mapping; + return cachedUserMapping; } @@ -172,6 +200,11 @@ public class BasicFileAuthenticationProvider extends SimpleAuthenticationProvide getAuthorizedConfigurations(Credentials credentials) throws GuacamoleException { + // Abort authorization if no user mapping exists + UserMapping userMapping = getUserMapping(); + if (userMapping == null) + return null; + // Validate and return info for given user and pass Authorization auth = getUserMapping().getAuthorization(credentials.getUsername()); if (auth != null && auth.validate(credentials.getUsername(), credentials.getPassword())) diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java index 799907b39..0816ae2c5 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/GuacamoleSession.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 @@ -22,18 +22,15 @@ package org.glyptodon.guacamole.net.basic; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.environment.Environment; import org.glyptodon.guacamole.net.GuacamoleTunnel; -import org.glyptodon.guacamole.net.auth.Credentials; +import org.glyptodon.guacamole.net.auth.AuthenticatedUser; import org.glyptodon.guacamole.net.auth.UserContext; -import org.glyptodon.guacamole.net.basic.properties.BasicGuacamoleProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,19 +48,15 @@ public class GuacamoleSession { private static final Logger logger = LoggerFactory.getLogger(GuacamoleSession.class); /** - * The credentials provided when the user authenticated. + * The user associated with this session. */ - private Credentials credentials; + 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; - - /** - * The current clipboard state. - */ - private final ClipboardState clipboardState = new ClipboardState(); + private List userContexts; /** * All currently-active tunnels, indexed by tunnel UUID. @@ -76,80 +69,110 @@ 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 * session. * - * @param credentials - * The credentials provided by the user during login. + * @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, Credentials credentials, - UserContext userContext) throws GuacamoleException { + public GuacamoleSession(Environment environment, + AuthenticatedUser authenticatedUser, + List userContexts) + throws GuacamoleException { this.lastAccessedTime = System.currentTimeMillis(); - this.credentials = credentials; - this.userContext = userContext; + this.authenticatedUser = authenticatedUser; + this.userContexts = userContexts; } /** - * Returns the credentials used when the user associated with this session - * authenticated. + * Returns the authenticated user associated with this session. * * @return - * The credentials used when the user associated with this session - * authenticated. + * The authenticated user associated with this session. */ - public Credentials getCredentials() { - return credentials; + public AuthenticatedUser getAuthenticatedUser() { + return authenticatedUser; } /** - * Replaces the credentials associated with this session with the given - * credentials. + * Replaces the authenticated user associated with this session with the + * given authenticated user. * - * @param credentials - * The credentials to associate with this session. + * @param authenticatedUser + * The authenticated user to associated with this session. */ - public void setCredentials(Credentials credentials) { - this.credentials = credentials; + public void setAuthenticatedUser(AuthenticatedUser authenticatedUser) { + this.authenticatedUser = authenticatedUser; } - + /** * Returns the UserContext associated with this session. * * @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 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 userContexts) { + this.userContexts = userContexts; } - /** - * Returns the ClipboardState associated with this session. - * - * @return The ClipboardState associated with this session. - */ - public ClipboardState getClipboardState() { - return clipboardState; - } - /** * Returns whether this session has any associated active tunnels. * diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/MonitoringGuacamoleReader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/MonitoringGuacamoleReader.java deleted file mode 100644 index 73bbc9fe3..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/MonitoringGuacamoleReader.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2014 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 - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic; - -import java.util.List; -import javax.xml.bind.DatatypeConverter; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.io.GuacamoleReader; -import org.glyptodon.guacamole.protocol.GuacamoleInstruction; - -/** - * GuacamoleReader implementation which watches for specific instructions, - * maintaining state based on the observed instructions. - * - * @author Michael Jumper - */ -public class MonitoringGuacamoleReader implements GuacamoleReader { - - /** - * The underlying GuacamoleReader. - */ - private final GuacamoleReader reader; - - /** - * Collection of all listeners which will receive events. - */ - private final ClipboardState clipboard; - - /** - * The index of the clipboard stream, if any. - */ - private String clipboard_stream_index = null; - - /** - * Creates a new MonitoringGuacamoleReader which watches the instructions - * read by the given GuacamoleReader, firing events when specific - * instructions are seen. - * - * @param clipboard The clipboard state to maintain. - * @param reader The reader to observe. - */ - public MonitoringGuacamoleReader(ClipboardState clipboard, - GuacamoleReader reader) { - this.clipboard = clipboard; - this.reader = reader; - } - - @Override - public boolean available() throws GuacamoleException { - return reader.available(); - } - - @Override - public char[] read() throws GuacamoleException { - - // Read single instruction, handle end-of-stream - GuacamoleInstruction instruction = readInstruction(); - if (instruction == null) - return null; - - return instruction.toString().toCharArray(); - - } - - @Override - public GuacamoleInstruction readInstruction() throws GuacamoleException { - - // Read single instruction, handle end-of-stream - GuacamoleInstruction instruction = reader.readInstruction(); - if (instruction == null) - return null; - - // If clipboard changing, reset clipboard state - if (instruction.getOpcode().equals("clipboard")) { - List args = instruction.getArgs(); - if (args.size() >= 2) { - clipboard_stream_index = args.get(0); - clipboard.begin(args.get(1)); - } - } - - // Add clipboard blobs to existing streams - else if (instruction.getOpcode().equals("blob")) { - List args = instruction.getArgs(); - if (args.size() >= 2 && args.get(0).equals(clipboard_stream_index)) { - String base64 = args.get(1); - clipboard.append(DatatypeConverter.parseBase64Binary(base64)); - } - } - - // Terminate and update clipboard at end of stream - else if (instruction.getOpcode().equals("end")) { - List args = instruction.getArgs(); - if (args.size() >= 1 && args.get(0).equals(clipboard_stream_index)) { - clipboard.commit(); - clipboard_stream_index = null; - } - } - - return instruction; - - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java index 974ed4a7e..9c20eedeb 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelRequestService.java @@ -24,13 +24,11 @@ package org.glyptodon.guacamole.net.basic; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService; import java.util.List; import org.glyptodon.guacamole.GuacamoleClientException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleSecurityException; import org.glyptodon.guacamole.environment.Environment; -import org.glyptodon.guacamole.io.GuacamoleReader; import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel; import org.glyptodon.guacamole.net.GuacamoleTunnel; import org.glyptodon.guacamole.net.auth.Connection; @@ -234,28 +232,6 @@ public class TunnelRequestService { */ private final long connectionStartTime = System.currentTimeMillis(); - @Override - public GuacamoleReader acquireReader() { - - // Monitor instructions which pertain to server-side events, if necessary - try { - if (environment.getProperty(ClipboardRESTService.INTEGRATION_ENABLED, false)) { - - ClipboardState clipboard = session.getClipboardState(); - return new MonitoringGuacamoleReader(clipboard, super.acquireReader()); - - } - } - catch (GuacamoleException e) { - logger.warn("Clipboard integration failed to initialize: {}", e.getMessage()); - logger.debug("Error setting up clipboard integration.", e); - } - - // Pass through by default. - return super.acquireReader(); - - } - @Override public void close() throws GuacamoleException { @@ -268,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 diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/AuthenticationProviderFacade.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/AuthenticationProviderFacade.java index 8a5b0d42d..227d43017 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/AuthenticationProviderFacade.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/AuthenticationProviderFacade.java @@ -23,7 +23,9 @@ package org.glyptodon.guacamole.net.basic.extension; import java.lang.reflect.InvocationTargetException; +import java.util.UUID; import org.glyptodon.guacamole.GuacamoleException; +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; @@ -52,6 +54,12 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { */ private final AuthenticationProvider authProvider; + /** + * The identifier to provide for the underlying authentication provider if + * the authentication provider could not be loaded. + */ + private final String facadeIdentifier = UUID.randomUUID().toString(); + /** * Creates a new AuthenticationProviderFacade which delegates all function * calls to an instance of the given AuthenticationProvider subclass. If @@ -118,7 +126,21 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { } @Override - public UserContext getUserContext(Credentials credentials) + public String getIdentifier() { + + // Ignore auth attempts if no auth provider could be loaded + if (authProvider == null) { + logger.warn("The authentication system could not be loaded. Please check for errors earlier in the logs."); + return facadeIdentifier; + } + + // Delegate to underlying auth provider + return authProvider.getIdentifier(); + + } + + @Override + public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException { // Ignore auth attempts if no auth provider could be loaded @@ -128,13 +150,13 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { } // Delegate to underlying auth provider - return authProvider.getUserContext(credentials); - + return authProvider.authenticateUser(credentials); + } @Override - public UserContext updateUserContext(UserContext context, Credentials credentials) - throws GuacamoleException { + public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser, + Credentials credentials) throws GuacamoleException { // Ignore auth attempts if no auth provider could be loaded if (authProvider == null) { @@ -143,7 +165,38 @@ public class AuthenticationProviderFacade implements AuthenticationProvider { } // Delegate to underlying auth provider - return authProvider.updateUserContext(context, credentials); + return authProvider.updateAuthenticatedUser(authenticatedUser, credentials); + + } + + @Override + public UserContext getUserContext(AuthenticatedUser authenticatedUser) + throws GuacamoleException { + + // Ignore auth attempts if no auth provider could be loaded + if (authProvider == null) { + logger.warn("User data retrieval attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs."); + return null; + } + + // Delegate to underlying auth provider + return authProvider.getUserContext(authenticatedUser); + + } + + @Override + public UserContext updateUserContext(UserContext context, + AuthenticatedUser authenticatedUser) + throws GuacamoleException { + + // Ignore auth attempts if no auth provider could be loaded + if (authProvider == null) { + logger.warn("User data refresh attempt denied because the authentication system could not be loaded. Please check for errors earlier in the logs."); + return null; + } + + // Delegate to underlying auth provider + return authProvider.updateUserContext(context, authenticatedUser); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java index 49d7a54ad..ac72a34ae 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/extension/ExtensionModule.java @@ -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 boundAuthenticationProvider = null; + private final List boundAuthenticationProviders = + new ArrayList(); /** * 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 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 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))); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java index c72040493..981233eb5 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/ObjectRetrievalService.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 @@ -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,43 @@ 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 identifier + * The unique identifier of the AuthenticationProvider that created the + * UserContext being retrieved. Only one UserContext per + * AuthenticationProvider can exist. + * + * @return + * The UserContext that was created by the AuthenticationProvider + * having the given identifier. + * + * @throws GuacamoleException + * If an error occurs while retrieving the UserContext, or if the + * UserContext does not exist. + */ + public UserContext retrieveUserContext(GuacamoleSession session, + String identifier) throws GuacamoleException { + + // Get list of UserContexts + List userContexts = session.getUserContexts(); + + // Locate and return the UserContext associated with the + // AuthenticationProvider having the given identifier, if any + for (UserContext userContext : userContexts) { + if (userContext.getAuthenticationProvider().getIdentifier().equals(identifier)) + return userContext; + } + + throw new GuacamoleResourceNotFoundException("Session not associated with authentication provider \"" + identifier + "\"."); + + } + /** * Retrieves a single user from the given user context. * diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java index 568630d7a..478c2182e 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java @@ -28,7 +28,6 @@ import com.google.inject.servlet.ServletModule; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import org.codehaus.jackson.jaxrs.JacksonJsonProvider; import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService; -import org.glyptodon.guacamole.net.basic.rest.clipboard.ClipboardRESTService; import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService; import org.glyptodon.guacamole.net.basic.rest.connectiongroup.ConnectionGroupRESTService; import org.glyptodon.guacamole.net.basic.rest.activeconnection.ActiveConnectionRESTService; @@ -58,7 +57,6 @@ public class RESTServletModule extends ServletModule { // Set up the API endpoints bind(ActiveConnectionRESTService.class); - bind(ClipboardRESTService.class); bind(ConnectionGroupRESTService.class); bind(ConnectionRESTService.class); bind(LanguageRESTService.class); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthenticationResult.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthenticationResult.java new file mode 100644 index 000000000..ab8e50d8d --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/APIAuthenticationResult.java @@ -0,0 +1,133 @@ +/* + * 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.net.basic.rest.auth; + +import java.util.Collections; +import java.util.List; + +/** + * A simple object which describes the result of an authentication operation, + * including the resulting token. + * + * @author James Muehlner + * @author Michael Jumper + */ +public class APIAuthenticationResult { + + /** + * The unique token generated for the user that authenticated. + */ + private final String authToken; + + /** + * The username of the user that authenticated. + */ + private final String username; + + /** + * The unique identifier of the data source from which this user account + * came. Although this user account may exist across several data sources + * (AuthenticationProviders), this will be the unique identifier of the + * AuthenticationProvider that authenticated this user for the current + * session. + */ + private final String dataSource; + + /** + * The identifiers of all data sources available to this user. + */ + private final List availableDataSources; + + /** + * Returns the unique authentication token which identifies the current + * session. + * + * @return + * The user's authentication token. + */ + public String getAuthToken() { + return authToken; + } + + /** + * Returns the user identified by the authentication token associated with + * the current session. + * + * @return + * The user identified by this authentication token. + */ + public String getUsername() { + return username; + } + + /** + * Returns the unique identifier of the data source associated with the user + * account associated with the current session. + * + * @return + * The unique identifier of the data source associated with the user + * account associated with the current session. + */ + public String getDataSource() { + return dataSource; + } + + /** + * Returns the identifiers of all data sources available to the user + * associated with the current session. + * + * @return + * The identifiers of all data sources available to the user associated + * with the current session. + */ + public List getAvailableDataSources() { + return availableDataSources; + } + + /** + * Create a new APIAuthenticationResult object containing the given data. + * + * @param authToken + * The unique token generated for the user that authenticated, to be + * used for the duration of their session. + * + * @param username + * The username of the user owning the given token. + * + * @param dataSource + * The unique identifier of the AuthenticationProvider which + * authenticated the user. + * + * @param availableDataSources + * The unique identifier of all AuthenticationProviders to which the + * user now has access. + */ + public APIAuthenticationResult(String authToken, String username, + String dataSource, List availableDataSources) { + this.authToken = authToken; + this.username = username; + this.dataSource = dataSource; + this.availableDataSources = Collections.unmodifiableList(availableDataSources); + } + +} 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 46eb082c4..b730a7163 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 @@ -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 getUserContexts(String authToken) + throws GuacamoleException { + return getGuacamoleSession(authToken).getUserContexts(); + } + } 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 5b87456ce..998cd5e44 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 @@ -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 @@ -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; @@ -37,11 +39,14 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.xml.bind.DatatypeConverter; import org.glyptodon.guacamole.GuacamoleException; +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.APIError; @@ -55,6 +60,7 @@ import org.slf4j.LoggerFactory; * A service for managing auth tokens via the Guacamole REST API. * * @author James Muehlner + * @author Michael Jumper */ @Path("/tokens") @Produces(MediaType.APPLICATION_JSON) @@ -67,10 +73,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 authProviders; /** * The map of auth tokens to sessions for the REST endpoints. @@ -136,53 +143,26 @@ public class TokenRESTService { } /** - * Authenticates a user, generates an auth token, associates that auth token - * with the user's UserContext for use by further requests. If an existing - * token is provided, the authentication procedure will attempt to update - * or reuse the provided token. - * + * Returns the credentials associated with the given request, using the + * provided username and password. + * + * @param request + * The request to use to derive the credentials. + * * @param username - * The username of the user who is to be authenticated. + * The username to associate with the credentials, or null if the + * username should be derived from the request. * * @param password - * The password of the user who is to be authenticated. + * The password to associate with the credentials, or null if the + * password should be derived from the request. * - * @param token - * An optional existing auth token for the user who is to be - * authenticated. - * - * @param consumedRequest - * The HttpServletRequest associated with the login attempt. The - * parameters of this request may not be accessible, as the request may - * have been fully consumed by JAX-RS. - * - * @param parameters - * A MultivaluedMap containing all parameters from the given HTTP - * request. All request parameters must be made available through this - * map, even if those parameters are no longer accessible within the - * now-fully-consumed HTTP request. - * - * @return The auth token for the newly logged-in user. - * @throws GuacamoleException If an error prevents successful login. + * @return + * A new Credentials object whose contents have been derived from the + * given request, along with the provided username and password. */ - @POST - @AuthProviderRESTExposure - public APIAuthToken createToken(@FormParam("username") String username, - @FormParam("password") String password, - @FormParam("token") String token, - @Context HttpServletRequest consumedRequest, - MultivaluedMap parameters) - throws GuacamoleException { - - // 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; + private Credentials getCredentials(HttpServletRequest request, + String username, String password) { // If no username/password given, try Authorization header if (username == null && password == null) { @@ -215,41 +195,151 @@ public class TokenRESTService { } } // end Authorization header fallback - + // Build credentials Credentials credentials = new Credentials(); credentials.setUsername(username); credentials.setPassword(password); credentials.setRequest(request); credentials.setSession(request.getSession(true)); - - UserContext userContext; - try { - // Update existing user context if session already exists - if (existingSession != null) - userContext = authProvider.updateUserContext(existingSession.getUserContext(), credentials); + return credentials; - /// Otherwise, generate a new user context - else { + } - userContext = authProvider.getUserContext(credentials); + /** + * 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 { - // Log successful authentication - if (userContext != null && logger.isInfoEnabled()) - logger.info("User \"{}\" successfully authenticated from {}.", - userContext.self().getIdentifier(), getLoggableAddress(request)); + 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; } - // Request standard username/password if no user context was produced - if (userContext == null) - throw new GuacamoleInvalidCredentialsException("Permission Denied.", - CredentialsInfo.USERNAME_PASSWORD); + // 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()) @@ -260,27 +350,172 @@ public class TokenRESTService { // Log anonymous authentication failures else if (logger.isDebugEnabled()) logger.debug("Anonymous authentication attempt from {} failed.", - getLoggableAddress(request), username); + 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 + * token is provided, the authentication procedure will attempt to update + * or reuse the provided token. + * + * @param username + * The username of the user who is to be authenticated. + * + * @param password + * The password of the user who is to be authenticated. + * + * @param token + * An optional existing auth token for the user who is to be + * authenticated. + * + * @param consumedRequest + * The HttpServletRequest associated with the login attempt. The + * parameters of this request may not be accessible, as the request may + * have been fully consumed by JAX-RS. + * + * @param parameters + * A MultivaluedMap containing all parameters from the given HTTP + * request. All request parameters must be made available through this + * map, even if those parameters are no longer accessible within the + * now-fully-consumed HTTP request. + * + * @return + * An authentication response object containing the possible-new auth + * token, as well as other related data. + * + * @throws GuacamoleException + * If an error prevents successful authentication. + */ + @POST + @AuthProviderRESTExposure + public APIAuthenticationResult createToken(@FormParam("username") String username, + @FormParam("password") String password, + @FormParam("token") String token, + @Context HttpServletRequest consumedRequest, + MultivaluedMap parameters) + throws GuacamoleException { + + // 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); + // Update existing session, if it exists String authToken; if (existingSession != null) { authToken = token; - existingSession.setCredentials(credentials); - existingSession.setUserContext(userContext); + 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, credentials, userContext)); + tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts)); + logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier()); } - - logger.debug("Login was successful for user \"{}\".", userContext.self().getIdentifier()); - return new APIAuthToken(authToken, userContext.self().getIdentifier()); + + // Build list of all available auth providers + List authProviderIdentifiers = new ArrayList(userContexts.size()); + for (UserContext userContext : userContexts) + authProviderIdentifiers.add(userContext.getAuthenticationProvider().getIdentifier()); + + // Return possibly-new auth token + return new APIAuthenticationResult( + authToken, + authenticatedUser.getIdentifier(), + authenticatedUser.getAuthenticationProvider().getIdentifier(), + authProviderIdentifiers + ); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/clipboard/ClipboardRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/clipboard/ClipboardRESTService.java deleted file mode 100644 index 6d60d238e..000000000 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/clipboard/ClipboardRESTService.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2013 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 - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.glyptodon.guacamole.net.basic.rest.clipboard; - -import com.google.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; -import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleUnsupportedException; -import org.glyptodon.guacamole.environment.Environment; -import org.glyptodon.guacamole.net.basic.ClipboardState; -import org.glyptodon.guacamole.net.basic.GuacamoleSession; -import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure; -import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService; -import org.glyptodon.guacamole.properties.BooleanGuacamoleProperty; - -/** - * A REST service for reading the current contents of the clipboard. - * - * @author Michael Jumper - */ -@Path("/clipboard") -public class ClipboardRESTService { - - /** - * The Guacamole server environment. - */ - @Inject - private Environment environment; - - /** - * A service for authenticating users from auth tokens. - */ - @Inject - private AuthenticationService authenticationService; - - /** - * The amount of time to wait for clipboard changes, in milliseconds. - */ - private static final int CLIPBOARD_TIMEOUT = 250; - - /** - * Whether clipboard integration is enabled. - */ - public static final BooleanGuacamoleProperty INTEGRATION_ENABLED = new BooleanGuacamoleProperty() { - - @Override - public String getName() { return "enable-clipboard-integration"; } - - }; - - @GET - @AuthProviderRESTExposure - public Response getClipboard(@QueryParam("token") String authToken) - throws GuacamoleException { - - // Only bother if actually enabled - if (environment.getProperty(INTEGRATION_ENABLED, false)) { - - // Get clipboard - GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); - final ClipboardState clipboard = session.getClipboardState(); - - // Send clipboard contents - synchronized (clipboard) { - clipboard.waitForContents(CLIPBOARD_TIMEOUT); - return Response.ok(clipboard.getContents(), - clipboard.getMimetype()).build(); - } - - } - - // Otherwise, inform not supported - else - throw new GuacamoleUnsupportedException("Clipboard integration not supported"); - - } - -} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java index a99134d91..37fda2bff 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/user/UserRESTService.java @@ -41,11 +41,11 @@ 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; import org.glyptodon.guacamole.net.auth.UserContext; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleCredentialsException; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; import org.glyptodon.guacamole.net.auth.permission.Permission; @@ -121,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. @@ -337,8 +331,16 @@ public class UserRESTService { credentials.setRequest(request); credentials.setSession(request.getSession(true)); - // Verify that the old password was correct - if (authProvider.getUserContext(credentials) == null) { + // Verify that the old password was correct + try { + if (userContext.getAuthenticationProvider().authenticateUser(credentials) == null) { + throw new APIException(APIError.Type.PERMISSION_DENIED, + "Permission denied."); + } + } + + // Pass through any credentials exceptions as simple permission denied + catch (GuacamoleCredentialsException e) { throw new APIException(APIError.Type.PERMISSION_DENIED, "Permission denied."); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/AuthorizeTagHandler.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/AuthorizeTagHandler.java similarity index 98% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/AuthorizeTagHandler.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/AuthorizeTagHandler.java index 18e224378..0b7788ab0 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/AuthorizeTagHandler.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/AuthorizeTagHandler.java @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.xml.user_mapping; +package org.glyptodon.guacamole.net.basic.xml.usermapping; import org.glyptodon.guacamole.net.basic.auth.Authorization; import org.glyptodon.guacamole.net.basic.auth.UserMapping; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ConnectionTagHandler.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ConnectionTagHandler.java similarity index 98% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ConnectionTagHandler.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ConnectionTagHandler.java index 7473b7316..2f2884ab1 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ConnectionTagHandler.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ConnectionTagHandler.java @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.xml.user_mapping; +package org.glyptodon.guacamole.net.basic.xml.usermapping; import org.glyptodon.guacamole.net.basic.auth.Authorization; import org.glyptodon.guacamole.xml.TagHandler; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ParamTagHandler.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ParamTagHandler.java similarity index 97% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ParamTagHandler.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ParamTagHandler.java index afc990ca8..f821491db 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ParamTagHandler.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ParamTagHandler.java @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.xml.user_mapping; +package org.glyptodon.guacamole.net.basic.xml.usermapping; import org.glyptodon.guacamole.xml.TagHandler; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ProtocolTagHandler.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ProtocolTagHandler.java similarity index 97% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ProtocolTagHandler.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ProtocolTagHandler.java index 14a5758d0..c50a26b4b 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/ProtocolTagHandler.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/ProtocolTagHandler.java @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.xml.user_mapping; +package org.glyptodon.guacamole.net.basic.xml.usermapping; import org.glyptodon.guacamole.xml.TagHandler; import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/UserMappingTagHandler.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/UserMappingTagHandler.java similarity index 97% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/UserMappingTagHandler.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/UserMappingTagHandler.java index 250f494c6..23c34471a 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/UserMappingTagHandler.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/UserMappingTagHandler.java @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package org.glyptodon.guacamole.net.basic.xml.user_mapping; +package org.glyptodon.guacamole.net.basic.xml.usermapping; import org.glyptodon.guacamole.net.basic.auth.UserMapping; import org.glyptodon.guacamole.xml.TagHandler; diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/package-info.java similarity index 95% rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/package-info.java rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/package-info.java index 8422f418d..08ec7bd3c 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/user_mapping/package-info.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/xml/usermapping/package-info.java @@ -23,5 +23,5 @@ /** * Classes related to parsing the user-mapping.xml file. */ -package org.glyptodon.guacamole.net.basic.xml.user_mapping; +package org.glyptodon.guacamole.net.basic.xml.usermapping; diff --git a/guacamole/src/main/webapp/app/auth/service/authenticationService.js b/guacamole/src/main/webapp/app/auth/service/authenticationService.js index cece87b17..8c17cbea2 100644 --- a/guacamole/src/main/webapp/app/auth/service/authenticationService.js +++ b/guacamole/src/main/webapp/app/auth/service/authenticationService.js @@ -57,7 +57,7 @@ angular.module('auth').factory('authenticationService', ['$injector', /** * The unique identifier of the local cookie which stores the user's - * current authentication token and user ID. + * current authentication token and username. * * @type String */ @@ -68,7 +68,7 @@ angular.module('auth').factory('authenticationService', ['$injector', * and given arbitrary parameters, returning a promise that succeeds only * if the authentication operation was successful. The resulting * authentication data can be retrieved later via getCurrentToken() or - * getCurrentUserID(). + * getCurrentUsername(). * * The provided parameters can be virtually any object, as each property * will be sent as an HTTP parameter in the authentication request. @@ -99,8 +99,9 @@ angular.module('auth').factory('authenticationService', ['$injector', // Store auth data $cookieStore.put(AUTH_COOKIE_ID, { - authToken : data.authToken, - userID : data.userID + authToken : data.authToken, + username : data.username, + dataSource : data.dataSource }); // Process is complete @@ -174,7 +175,7 @@ angular.module('auth').factory('authenticationService', ['$injector', * its properties will be included as parameters in the update request. * This function returns a promise that succeeds only if the authentication * operation was successful. The resulting authentication data can be - * retrieved later via getCurrentToken() or getCurrentUserID(). + * retrieved later via getCurrentToken() or getCurrentUsername(). * * If there is no current auth token, this function behaves identically to * authenticate(), and makes a general authentication request. @@ -209,7 +210,7 @@ angular.module('auth').factory('authenticationService', ['$injector', * with a username and password, ignoring any currently-stored token, * returning a promise that succeeds only if the login operation was * successful. The resulting authentication data can be retrieved later - * via getCurrentToken() or getCurrentUserID(). + * via getCurrentToken() or getCurrentUsername(). * * @param {String} username * The username to log in with. @@ -254,19 +255,19 @@ angular.module('auth').factory('authenticationService', ['$injector', }; /** - * Returns the user ID of the current user. If the current user is not - * logged in, this ID may not be valid. + * Returns the username of the current user. If the current user is not + * logged in, this value may not be valid. * * @returns {String} - * The user ID of the current user, or null if no authentication data + * The username of the current user, or null if no authentication data * is present. */ - service.getCurrentUserID = function getCurrentUserID() { + service.getCurrentUsername = function getCurrentUsername() { - // Return user ID, if available + // Return username, if available var authData = $cookieStore.get(AUTH_COOKIE_ID); if (authData) - return authData.userID; + return authData.username; // No auth data present return null; @@ -293,5 +294,45 @@ angular.module('auth').factory('authenticationService', ['$injector', }; + /** + * Returns the identifier of the data source that authenticated the current + * user. If the current user is not logged in, this value may not be valid. + * + * @returns {String} + * The identifier of the data source that authenticated the current + * user, or null if no authentication data is present. + */ + service.getDataSource = function getDataSource() { + + // Return data source, if available + var authData = $cookieStore.get(AUTH_COOKIE_ID); + if (authData) + return authData.dataSource; + + // No auth data present + return null; + + }; + + /** + * Returns the identifiers of all data sources available to the current + * user. If the current user is not logged in, this value may not be valid. + * + * @returns {String[]} + * The identifiers of all data sources availble to the current user, + * or null if no authentication data is present. + */ + service.getAvailableDataSources = function getAvailableDataSources() { + + // Return data sources, if available + var authData = $cookieStore.get(AUTH_COOKIE_ID); + if (authData) + return authData.availableDataSources; + + // No auth data present + return null; + + }; + return service; }]); diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js index 04694f2dd..04e1066d4 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionController.js @@ -190,7 +190,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i }); // Query the user's permissions for the current connection - permissionService.getPermissions(authenticationService.getCurrentUserID()) + permissionService.getPermissions(authenticationService.getCurrentUsername()) .success(function permissionsReceived(permissions) { $scope.permissions = permissions; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js index 02119c4d0..64fb03b08 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageConnectionGroupController.js @@ -128,7 +128,7 @@ angular.module('manage').controller('manageConnectionGroupController', ['$scope' }); // Query the user's permissions for the current connection group - permissionService.getPermissions(authenticationService.getCurrentUserID()) + permissionService.getPermissions(authenticationService.getCurrentUsername()) .success(function permissionsReceived(permissions) { $scope.permissions = permissions; diff --git a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js index e1e89e82c..00c09c5e1 100644 --- a/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js +++ b/guacamole/src/main/webapp/app/manage/controllers/manageUserController.js @@ -153,7 +153,7 @@ angular.module('manage').controller('manageUserController', ['$scope', '$injecto }); // Query the user's permissions for the current connection - permissionService.getPermissions(authenticationService.getCurrentUserID()) + permissionService.getPermissions(authenticationService.getCurrentUsername()) .success(function permissionsReceived(permissions) { $scope.permissions = permissions; diff --git a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js index 4efe62cf0..6cc80c885 100644 --- a/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js +++ b/guacamole/src/main/webapp/app/navigation/directives/guacUserMenu.js @@ -78,7 +78,7 @@ angular.module('navigation').directive('guacUserMenu', [function guacUserMenu() * * @type String */ - $scope.username = authenticationService.getCurrentUserID(); + $scope.username = authenticationService.getCurrentUsername(); /** * The available main pages for the current user. diff --git a/guacamole/src/main/webapp/app/navigation/services/userPageService.js b/guacamole/src/main/webapp/app/navigation/services/userPageService.js index 86453c576..9489581b0 100644 --- a/guacamole/src/main/webapp/app/navigation/services/userPageService.js +++ b/guacamole/src/main/webapp/app/navigation/services/userPageService.js @@ -156,7 +156,7 @@ angular.module('navigation').factory('userPageService', ['$injector', PermissionSet.removeConnectionGroupPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, ConnectionGroup.ROOT_IDENTIFIER); // Ignore permission to update self - PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, authenticationService.getCurrentUserID()); + PermissionSet.removeUserPermission(permissions, PermissionSet.ObjectPermissionType.UPDATE, authenticationService.getCurrentUsername()); // Determine whether the current user needs access to the user management UI var canManageUsers = @@ -247,7 +247,7 @@ angular.module('navigation').factory('userPageService', ['$injector', // Retrieve current permissions, resolving main pages if possible // Resolve promise using settings pages derived from permissions - permissionService.getPermissions(authenticationService.getCurrentUserID()) + permissionService.getPermissions(authenticationService.getCurrentUsername()) .success(function permissionsRetrieved(permissions) { deferred.resolve(generateSettingsPages(permissions)); }); @@ -328,7 +328,7 @@ angular.module('navigation').factory('userPageService', ['$injector', }); // Retrieve current permissions, resolving main pages if possible - permissionService.getPermissions(authenticationService.getCurrentUserID()) + permissionService.getPermissions(authenticationService.getCurrentUsername()) .success(function permissionsRetrieved(retrievedPermissions) { permissions = retrievedPermissions; resolveMainPages(); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js index a900aed11..e1b77893f 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsConnections.js @@ -48,7 +48,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe var permissionService = $injector.get('permissionService'); // Identifier of the current user - var currentUserID = authenticationService.getCurrentUserID(); + var currentUsername = authenticationService.getCurrentUsername(); /** * An action to be provided along with the object sent to @@ -120,7 +120,7 @@ angular.module('settings').directive('guacSettingsConnections', [function guacSe }; // Retrieve current permissions - permissionService.getPermissions(currentUserID) + permissionService.getPermissions(currentUsername) .success(function permissionsRetrieved(permissions) { $scope.permissions = permissions; diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js index 482529a37..30a52e265 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsPreferences.js @@ -64,7 +64,7 @@ angular.module('settings').directive('guacSettingsPreferences', [function guacSe * * @type String */ - var username = authenticationService.getCurrentUserID(); + var username = authenticationService.getCurrentUsername(); /** * All currently-set preferences, or their defaults if not yet set. diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js index e478fbd69..3635d38cb 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsSessions.js @@ -187,7 +187,7 @@ angular.module('settings').directive('guacSettingsSessions', [function guacSetti }; // Query the user's permissions - permissionService.getPermissions(authenticationService.getCurrentUserID()) + permissionService.getPermissions(authenticationService.getCurrentUsername()) .success(function permissionsReceived(retrievedPermissions) { $scope.permissions = retrievedPermissions; }); diff --git a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js index 20c877b46..1d0f411fb 100644 --- a/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js +++ b/guacamole/src/main/webapp/app/settings/directives/guacSettingsUsers.js @@ -48,7 +48,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings var userService = $injector.get('userService'); // Identifier of the current user - var currentUserID = authenticationService.getCurrentUserID(); + var currentUsername = authenticationService.getCurrentUsername(); /** * An action to be provided along with the object sent to @@ -118,7 +118,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings }; // Retrieve current permissions - permissionService.getPermissions(currentUserID) + permissionService.getPermissions(currentUsername) .success(function permissionsRetrieved(permissions) { $scope.permissions = permissions; @@ -147,7 +147,7 @@ angular.module('settings').directive('guacSettingsUsers', [function guacSettings // Display only other users, not self $scope.users = users.filter(function isNotSelf(user) { - return user.username !== currentUserID; + return user.username !== currentUsername; }); });