Merge pull request #250 from glyptodon/multiple-ext

GUAC-586: Load multiple AuthenticationProviders
This commit is contained in:
James Muehlner
2015-09-01 17:17:36 -07:00
45 changed files with 1449 additions and 620 deletions

View File

@@ -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);

View File

@@ -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);
}
}

View File

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

View File

@@ -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<User> getUserDirectory() throws GuacamoleException {
return userDirectory;

View File

@@ -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<ModeledUser, User
* the necessary additional parameters to reset the user's password, the
* password is reset.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the user is being
* retrieved.
*
* @param credentials
* The credentials to use when locating the user.
*
* @return
* The existing ModeledUser object if the credentials given are valid,
* null otherwise.
* An AuthenticatedUser containing the existing ModeledUser object if
* the credentials given are valid, null otherwise.
*
* @throws GuacamoleException
* If the provided credentials to not conform to expectations.
*/
public ModeledUser retrieveUser(Credentials credentials)
throws GuacamoleException {
public AuthenticatedUser retrieveAuthenticatedUser(AuthenticationProvider authenticationProvider,
Credentials credentials) throws GuacamoleException {
// Get username and password
String username = credentials.getUsername();
@@ -298,7 +303,7 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
// Create corresponding user object, set up cyclic reference
ModeledUser user = getObjectInstance(null, userModel);
user.setCurrentUser(new AuthenticatedUser(user, credentials));
user.setCurrentUser(new AuthenticatedUser(authenticationProvider, user, credentials));
// Verify user account is still valid as of today
if (!user.isAccountValid())
@@ -343,6 +348,40 @@ public class UserService extends ModeledDirectoryObjectService<ModeledUser, User
}
// Return now-authenticated user
return user.getCurrentUser();
}
/**
* Retrieves the user corresponding to the given AuthenticatedUser from the
* database.
*
* @param authenticatedUser
* The AuthenticatedUser to retrieve the corresponding ModeledUser of.
*
* @return
* The ModeledUser which corresponds to the given AuthenticatedUser, or
* null if no such user exists.
*/
public ModeledUser retrieveUser(org.glyptodon.guacamole.net.auth.AuthenticatedUser authenticatedUser) {
// If we already queried this user, return that rather than querying again
if (authenticatedUser instanceof AuthenticatedUser)
return ((AuthenticatedUser) authenticatedUser).getUser();
// Get username
String username = authenticatedUser.getIdentifier();
// Retrieve corresponding user model, if such a user exists
UserModel userModel = userMapper.selectOne(username);
if (userModel == null)
return null;
// Create corresponding user object, set up cyclic reference
ModeledUser user = getObjectInstance(null, userModel);
user.setCurrentUser(new AuthenticatedUser(authenticatedUser.getAuthenticationProvider(), user, authenticatedUser.getCredentials()));
// Return already-authenticated user
return user;
}

View File

@@ -31,9 +31,10 @@ import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule;
import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.ConfigurableGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.user.UserContextService;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticationProviderService;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.environment.LocalEnvironment;
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -184,25 +185,50 @@ public class MySQLAuthenticationProvider implements AuthenticationProvider {
new MySQLAuthenticationProviderModule(environment),
// Configure JDBC authentication core
new JDBCAuthenticationProviderModule(environment, getTunnelService(environment))
new JDBCAuthenticationProviderModule(this, environment,
getTunnelService(environment))
);
}
@Override
public UserContext getUserContext(Credentials credentials)
public String getIdentifier() {
return "mysql";
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Create AuthenticatedUser based on credentials, if valid
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.authenticateUser(this, credentials);
}
@Override
public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// No need to update authenticated users
return authenticatedUser;
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// Create UserContext based on credentials, if valid
UserContextService userContextService = injector.getInstance(UserContextService.class);
return userContextService.getUserContext(credentials);
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.getUserContext(authenticatedUser);
}
@Override
public UserContext updateUserContext(UserContext context,
Credentials credentials) throws GuacamoleException {
AuthenticatedUser authenticatedUser) throws GuacamoleException {
// No need to update the context
return context;

View File

@@ -31,9 +31,10 @@ import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.auth.jdbc.JDBCAuthenticationProviderModule;
import org.glyptodon.guacamole.auth.jdbc.tunnel.ConfigurableGuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.glyptodon.guacamole.auth.jdbc.user.UserContextService;
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticationProviderService;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.environment.LocalEnvironment;
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -184,25 +185,50 @@ public class PostgreSQLAuthenticationProvider implements AuthenticationProvider
new PostgreSQLAuthenticationProviderModule(environment),
// Configure JDBC authentication core
new JDBCAuthenticationProviderModule(environment, getTunnelService(environment))
new JDBCAuthenticationProviderModule(this, environment,
getTunnelService(environment))
);
}
@Override
public UserContext getUserContext(Credentials credentials)
public String getIdentifier() {
return "postgresql";
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Create AuthenticatedUser based on credentials, if valid
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.authenticateUser(this, credentials);
}
@Override
public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// No need to update authenticated users
return authenticatedUser;
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// Create UserContext based on credentials, if valid
UserContextService userContextService = injector.getInstance(UserContextService.class);
return userContextService.getUserContext(credentials);
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.getUserContext(authenticatedUser);
}
@Override
public UserContext updateUserContext(UserContext context,
Credentials credentials) throws GuacamoleException {
AuthenticatedUser authenticatedUser) throws GuacamoleException {
// No need to update the context
return context;

View File

@@ -74,6 +74,11 @@ public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider {
environment = new LocalEnvironment();
}
@Override
public String getIdentifier() {
return "ldap";
}
// Courtesy of OWASP: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
private static String escapeLDAPSearchFilter(String filter) {
StringBuilder sb = new StringBuilder();

View File

@@ -122,6 +122,11 @@ public class NoAuthenticationProvider extends SimpleAuthenticationProvider {
environment = new LocalEnvironment();
}
@Override
public String getIdentifier() {
return "noauth";
}
/**
* Retrieves the configuration file, as defined within guacamole.properties.
*

View File

@@ -119,7 +119,8 @@ public class LocalEnvironment implements Environment {
}
catch (IOException e) {
throw new GuacamoleServerException("Error reading guacamole.properties", e);
logger.warn("The guacamole.properties file within GUACAMOLE_HOME cannot be read: {}", e.getMessage());
logger.debug("Error reading guacamole.properties.", e);
}
// Read all protocols
@@ -190,11 +191,8 @@ public class LocalEnvironment implements Environment {
*
* @return
* A map of all available protocols.
*
* @throws GuacamoleException
* If an error occurs while reading the various protocol JSON files.
*/
private Map<String, ProtocolInfo> readProtocols() throws GuacamoleException {
private Map<String, ProtocolInfo> readProtocols() {
// Map of all available protocols
Map<String, ProtocolInfo> protocols = new HashMap<String, ProtocolInfo>();

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

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

View File

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

View File

@@ -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<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration>
getFilteredAuthorizedConfigurations(Credentials credentials)
throws GuacamoleException {
// Get configurations
Map<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration>
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<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration> 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;

View File

@@ -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<String, GuacamoleConfiguration> configs) {
this(UUID.randomUUID().toString(), configs);
public SimpleUserContext(AuthenticationProvider authProvider,
Map<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration> configs) {
public SimpleUserContext(AuthenticationProvider authProvider,
String username, Map<String, GuacamoleConfiguration> configs) {
Collection<String> connectionIdentifiers = new ArrayList<String>(configs.size());
Collection<String> 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<User> getUserDirectory()
throws GuacamoleException {

View File

@@ -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()))

View File

@@ -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<UserContext> 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<UserContext> 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<UserContext> getUserContexts() {
return Collections.unmodifiableList(userContexts);
}
/**
* Replaces all UserContexts associated with this session with the given
* List of UserContexts.
*
* @param userContexts
* The List of UserContexts to associate with this session.
*/
public void setUserContexts(List<UserContext> userContexts) {
this.userContexts = userContexts;
}
/**
* 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.
*

View File

@@ -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<String> 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<String> 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<String> args = instruction.getArgs();
if (args.size() >= 1 && args.get(0).equals(clipboard_stream_index)) {
clipboard.commit();
clipboard_stream_index = null;
}
}
return instruction;
}
}

View File

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

View File

@@ -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);
}

View File

@@ -22,6 +22,7 @@
package org.glyptodon.guacamole.net.basic.extension;
import com.google.inject.Provides;
import com.google.inject.servlet.ServletModule;
import java.io.File;
import java.io.FileFilter;
@@ -91,10 +92,10 @@ public class ExtensionModule extends ServletModule {
private final Environment environment;
/**
* The currently-bound authentication provider, if any. At the moment, we
* only support one authentication provider loaded at any one time.
* All currently-bound authentication providers, if any.
*/
private Class<? extends AuthenticationProvider> boundAuthenticationProvider = null;
private final List<AuthenticationProvider> boundAuthenticationProviders =
new ArrayList<AuthenticationProvider>();
/**
* Service for adding and retrieving language resources.
@@ -179,40 +180,24 @@ public class ExtensionModule extends ServletModule {
/**
* Binds the given AuthenticationProvider class such that any service
* requiring access to the AuthenticationProvider can obtain it via
* injection.
* injection, along with any other bound AuthenticationProviders.
*
* @param authenticationProvider
* The AuthenticationProvider class to bind.
*/
private void bindAuthenticationProvider(Class<? extends AuthenticationProvider> authenticationProvider) {
// Choose auth provider for binding if not already chosen
if (boundAuthenticationProvider == null)
boundAuthenticationProvider = authenticationProvider;
// If an auth provider is already chosen, skip and warn
else {
logger.debug("Ignoring AuthenticationProvider \"{}\".", authenticationProvider);
logger.warn("Only one authentication extension may be used at a time. Please "
+ "make sure that only one authentication extension is present "
+ "within the GUACAMOLE_HOME/" + EXTENSIONS_DIRECTORY + " "
+ "directory, and that you are not also specifying the deprecated "
+ "\"auth-provider\" property within guacamole.properties.");
return;
}
// Bind authentication provider
logger.debug("Binding AuthenticationProvider \"{}\".", authenticationProvider);
bind(AuthenticationProvider.class).toInstance(new AuthenticationProviderFacade(authenticationProvider));
logger.debug("[{}] Binding AuthenticationProvider \"{}\".",
boundAuthenticationProviders.size(), authenticationProvider.getName());
boundAuthenticationProviders.add(new AuthenticationProviderFacade(authenticationProvider));
}
/**
* Binds each of the the given AuthenticationProvider classes such that any
* service requiring access to the AuthenticationProvider can obtain it via
* injection. Note that, as multiple simultaneous authentication providers
* are not currently supported, attempting to bind more than one
* authentication provider will result in warnings being logged.
* injection.
*
* @param authProviders
* The AuthenticationProvider classes to bind.
@@ -225,6 +210,18 @@ public class ExtensionModule extends ServletModule {
}
/**
* Returns a list of all currently-bound AuthenticationProvider instances.
*
* @return
* A List of all currently-bound AuthenticationProvider. The List is
* not modifiable.
*/
@Provides
public List<AuthenticationProvider> getAuthenticationProviders() {
return Collections.unmodifiableList(boundAuthenticationProviders);
}
/**
* Serves each of the given resources as a language resource. Language
* resources are served from within the "/translations" directory as JSON
@@ -415,11 +412,8 @@ public class ExtensionModule extends ServletModule {
// Load all extensions
loadExtensions(javaScriptResources, cssResources);
// Bind basic auth if nothing else chosen/provided
if (boundAuthenticationProvider == null) {
logger.info("Using default, \"basic\", XML-driven authentication.");
bindAuthenticationProvider(BasicFileAuthenticationProvider.class);
}
// Always bind basic auth last
bindAuthenticationProvider(BasicFileAuthenticationProvider.class);
// Dynamically generate app.js and app.css from extensions
serve("/app.js").with(new ResourceServlet(new SequenceResource(javaScriptResources)));

View File

@@ -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<UserContext> 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.
*

View File

@@ -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);

View File

@@ -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<String> 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<String> 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<String> availableDataSources) {
this.authToken = authToken;
this.username = username;
this.dataSource = dataSource;
this.availableDataSources = Collections.unmodifiableList(availableDataSources);
}
}

View File

@@ -23,6 +23,7 @@
package org.glyptodon.guacamole.net.basic.rest.auth;
import com.google.inject.Inject;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
import org.glyptodon.guacamole.net.auth.UserContext;
@@ -77,5 +78,24 @@ public class AuthenticationService {
public UserContext getUserContext(String authToken) throws GuacamoleException {
return getGuacamoleSession(authToken).getUserContext();
}
/**
* Returns all UserContexts associated with a given auth token, if the auth
* token represents a currently logged in user. Throws an unauthorized
* error otherwise.
*
* @param authToken
* The auth token to check against the map of logged in users.
*
* @return
* A List of all UserContexts associated with the provided auth token.
*
* @throws GuacamoleException
* If the auth token does not correspond to any logged in user.
*/
public List<UserContext> getUserContexts(String authToken)
throws GuacamoleException {
return getGuacamoleSession(authToken).getUserContexts();
}
}

View File

@@ -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<AuthenticationProvider> 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<String, String> 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<UserContext> getUserContexts(GuacamoleSession existingSession,
AuthenticatedUser authenticatedUser) throws GuacamoleException {
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
// If UserContexts already exist, update them and add to the list
if (existingSession != null) {
// Update all old user contexts
List<UserContext> oldUserContexts = existingSession.getUserContexts();
for (UserContext oldUserContext : oldUserContexts) {
// Update existing UserContext
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
UserContext userContext = authProvider.updateUserContext(oldUserContext, authenticatedUser);
// Add to available data, if successful
if (userContext != null)
userContexts.add(userContext);
// If unsuccessful, log that this happened, as it may be a bug
else
logger.debug("AuthenticationProvider \"{}\" retroactively destroyed its UserContext.",
authProvider.getClass().getName());
}
}
// Otherwise, create new UserContexts from available AuthenticationProviders
else {
// Get UserContexts from each available AuthenticationProvider
for (AuthenticationProvider authProvider : authProviders) {
// Generate new UserContext
UserContext userContext = authProvider.getUserContext(authenticatedUser);
// Add to available data, if successful
if (userContext != null)
userContexts.add(userContext);
}
}
return userContexts;
}
/**
* Authenticates a user, generates an auth token, associates that auth token
* with the user's UserContext for use by further requests. If an existing
* 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<String, String> 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<UserContext> 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<String> authProviderIdentifiers = new ArrayList<String>(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
);
}

View File

@@ -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");
}
}

View File

@@ -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.");
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}]);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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.

View File

@@ -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();

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
});

View File

@@ -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;
});
});