mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 13:41:21 +00:00
Merge pull request #278 from glyptodon/invalidate-sessions
GUAC-1364: Implicitly invalidate sessions when an "unauthorized" exception is thrown
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
package org.glyptodon.guacamole.net.auth.credentials;
|
package org.glyptodon.guacamole.net.auth.credentials;
|
||||||
|
|
||||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A security-related exception thrown when access is denied to a user because
|
* A security-related exception thrown when access is denied to a user because
|
||||||
@@ -31,7 +31,7 @@ import org.glyptodon.guacamole.GuacamoleSecurityException;
|
|||||||
*
|
*
|
||||||
* @author Michael Jumper
|
* @author Michael Jumper
|
||||||
*/
|
*/
|
||||||
public class GuacamoleCredentialsException extends GuacamoleSecurityException {
|
public class GuacamoleCredentialsException extends GuacamoleUnauthorizedException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information describing the form of valid credentials.
|
* Information describing the form of valid credentials.
|
||||||
|
@@ -32,8 +32,7 @@ import org.glyptodon.guacamole.environment.Environment;
|
|||||||
import org.glyptodon.guacamole.environment.LocalEnvironment;
|
import org.glyptodon.guacamole.environment.LocalEnvironment;
|
||||||
import org.glyptodon.guacamole.net.basic.extension.ExtensionModule;
|
import org.glyptodon.guacamole.net.basic.extension.ExtensionModule;
|
||||||
import org.glyptodon.guacamole.net.basic.log.LogModule;
|
import org.glyptodon.guacamole.net.basic.log.LogModule;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.RESTAuthModule;
|
import org.glyptodon.guacamole.net.basic.rest.RESTServiceModule;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.RESTServletModule;
|
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.BasicTokenSessionMap;
|
import org.glyptodon.guacamole.net.basic.rest.auth.BasicTokenSessionMap;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.TokenSessionMap;
|
import org.glyptodon.guacamole.net.basic.rest.auth.TokenSessionMap;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -85,8 +84,7 @@ public class BasicServletContextListener extends GuiceServletContextListener {
|
|||||||
new EnvironmentModule(environment),
|
new EnvironmentModule(environment),
|
||||||
new LogModule(environment),
|
new LogModule(environment),
|
||||||
new ExtensionModule(environment),
|
new ExtensionModule(environment),
|
||||||
new RESTAuthModule(sessionMap),
|
new RESTServiceModule(sessionMap),
|
||||||
new RESTServletModule(),
|
|
||||||
new TunnelModule()
|
new TunnelModule()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ import com.google.inject.Singleton;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.glyptodon.guacamole.GuacamoleException;
|
import org.glyptodon.guacamole.GuacamoleException;
|
||||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||||
|
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||||
import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel;
|
import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel;
|
||||||
import org.glyptodon.guacamole.net.GuacamoleTunnel;
|
import org.glyptodon.guacamole.net.GuacamoleTunnel;
|
||||||
import org.glyptodon.guacamole.net.auth.Connection;
|
import org.glyptodon.guacamole.net.auth.Connection;
|
||||||
@@ -211,6 +212,12 @@ public class TunnelRequestService {
|
|||||||
* @param tunnel
|
* @param tunnel
|
||||||
* The connected tunnel to wrap and monitor.
|
* The connected tunnel to wrap and monitor.
|
||||||
*
|
*
|
||||||
|
* @param authToken
|
||||||
|
* The authentication token associated with the given session. If
|
||||||
|
* provided, this token will be automatically invalidated (and the
|
||||||
|
* corresponding session destroyed) if tunnel errors imply that the
|
||||||
|
* user is no longer authorized.
|
||||||
|
*
|
||||||
* @param session
|
* @param session
|
||||||
* The Guacamole session to associate the tunnel with.
|
* The Guacamole session to associate the tunnel with.
|
||||||
*
|
*
|
||||||
@@ -228,9 +235,10 @@ public class TunnelRequestService {
|
|||||||
* @throws GuacamoleException
|
* @throws GuacamoleException
|
||||||
* If an error occurs while obtaining the tunnel.
|
* If an error occurs while obtaining the tunnel.
|
||||||
*/
|
*/
|
||||||
protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleSession session,
|
protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel,
|
||||||
GuacamoleTunnel tunnel, final TunnelRequest.Type type,
|
final String authToken, final GuacamoleSession session,
|
||||||
final String id) throws GuacamoleException {
|
final TunnelRequest.Type type, final String id)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
// Monitor tunnel closure and data
|
// Monitor tunnel closure and data
|
||||||
GuacamoleTunnel monitoredTunnel = new DelegatingGuacamoleTunnel(tunnel) {
|
GuacamoleTunnel monitoredTunnel = new DelegatingGuacamoleTunnel(tunnel) {
|
||||||
@@ -268,9 +276,25 @@ public class TunnelRequestService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close and clean up tunnel
|
try {
|
||||||
session.removeTunnel(getUUID().toString());
|
|
||||||
super.close();
|
// Close and clean up tunnel
|
||||||
|
session.removeTunnel(getUUID().toString());
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure any associated session is invalidated if unauthorized
|
||||||
|
catch (GuacamoleUnauthorizedException e) {
|
||||||
|
|
||||||
|
// If there is an associated auth token, invalidate it
|
||||||
|
if (authenticationService.destroyGuacamoleSession(authToken))
|
||||||
|
logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
|
||||||
|
|
||||||
|
// Continue with exception processing
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,13 +329,30 @@ public class TunnelRequestService {
|
|||||||
String authProviderIdentifier = request.getAuthenticationProviderIdentifier();
|
String authProviderIdentifier = request.getAuthenticationProviderIdentifier();
|
||||||
GuacamoleClientInformation info = getClientInformation(request);
|
GuacamoleClientInformation info = getClientInformation(request);
|
||||||
|
|
||||||
// Create connected tunnel using provided connection ID and client information
|
|
||||||
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
|
||||||
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
|
||||||
GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
|
|
||||||
|
|
||||||
// Associate tunnel with session
|
try {
|
||||||
return createAssociatedTunnel(session, tunnel, type, id);
|
|
||||||
|
// Create connected tunnel using provided connection ID and client information
|
||||||
|
GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
|
||||||
|
|
||||||
|
// Associate tunnel with session
|
||||||
|
return createAssociatedTunnel(tunnel, authToken, session, type, id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure any associated session is invalidated if unauthorized
|
||||||
|
catch (GuacamoleUnauthorizedException e) {
|
||||||
|
|
||||||
|
// If there is an associated auth token, invalidate it
|
||||||
|
if (authenticationService.destroyGuacamoleSession(authToken))
|
||||||
|
logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
|
||||||
|
|
||||||
|
// Continue with exception processing
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.AuthTokenGenerator;
|
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator;
|
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.TokenSessionMap;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Guice Module for setting up authentication-specific dependency injection.
|
|
||||||
*
|
|
||||||
* @author James Muehlner
|
|
||||||
* @author Michael Jumper
|
|
||||||
*/
|
|
||||||
public class RESTAuthModule extends AbstractModule {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logger for this class.
|
|
||||||
*/
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(RESTAuthModule.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton instance of TokenSessionMap.
|
|
||||||
*/
|
|
||||||
private final TokenSessionMap tokenSessionMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a module which handles binding of authentication-related
|
|
||||||
* objects, including the singleton TokenSessionMap.
|
|
||||||
*
|
|
||||||
* @param tokenSessionMap
|
|
||||||
* An instance of TokenSessionMap to inject as a singleton wherever
|
|
||||||
* needed.
|
|
||||||
*/
|
|
||||||
public RESTAuthModule(TokenSessionMap tokenSessionMap) {
|
|
||||||
this.tokenSessionMap = tokenSessionMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
|
|
||||||
// Bind session map
|
|
||||||
bind(TokenSessionMap.class).toInstance(tokenSessionMap);
|
|
||||||
|
|
||||||
// Bind low-level services
|
|
||||||
bind(AuthenticationService.class);
|
|
||||||
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -22,14 +22,21 @@
|
|||||||
|
|
||||||
package org.glyptodon.guacamole.net.basic.rest;
|
package org.glyptodon.guacamole.net.basic.rest;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
import org.aopalliance.intercept.MethodInterceptor;
|
import org.aopalliance.intercept.MethodInterceptor;
|
||||||
import org.aopalliance.intercept.MethodInvocation;
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
import org.glyptodon.guacamole.GuacamoleClientException;
|
import org.glyptodon.guacamole.GuacamoleClientException;
|
||||||
import org.glyptodon.guacamole.GuacamoleException;
|
import org.glyptodon.guacamole.GuacamoleException;
|
||||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||||
|
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||||
|
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -45,14 +52,128 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class RESTExceptionWrapper implements MethodInterceptor {
|
public class RESTExceptionWrapper implements MethodInterceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(RESTExceptionWrapper.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for authenticating users and managing their Guacamole sessions.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private AuthenticationService authenticationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the given set of annotations describes an HTTP
|
||||||
|
* request parameter of the given name. For a parameter to be associated
|
||||||
|
* with an HTTP request parameter, it must be annotated with either the
|
||||||
|
* <code>@QueryParam</code> or <code>@FormParam</code> annotations.
|
||||||
|
*
|
||||||
|
* @param annotations
|
||||||
|
* The annotations associated with the Java parameter being checked.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of the HTTP request parameter.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* true if the given set of annotations describes an HTTP request
|
||||||
|
* parameter having the given name, false otherwise.
|
||||||
|
*/
|
||||||
|
private boolean isRequestParameter(Annotation[] annotations, String name) {
|
||||||
|
|
||||||
|
// Search annotations for associated HTTP parameters
|
||||||
|
for (Annotation annotation : annotations) {
|
||||||
|
|
||||||
|
// Check if parameter is associated with the HTTP query string
|
||||||
|
if (annotation instanceof QueryParam && name.equals(((QueryParam) annotation).value()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Failing that, check whether the parameter is associated with the
|
||||||
|
// HTTP request body
|
||||||
|
if (annotation instanceof FormParam && name.equals(((FormParam) annotation).value()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// No parameter annotations are present
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication token that was passed in the given method
|
||||||
|
* invocation. If the given method invocation is not associated with an
|
||||||
|
* HTTP request (it lacks the appropriate JAX-RS annotations) or there is
|
||||||
|
* no authentication token, null is returned.
|
||||||
|
*
|
||||||
|
* @param invocation
|
||||||
|
* The method invocation whose corresponding authentication token
|
||||||
|
* should be determined.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The authentication token passed in the given method invocation, or
|
||||||
|
* null if there is no such token.
|
||||||
|
*/
|
||||||
|
private String getAuthenticationToken(MethodInvocation invocation) {
|
||||||
|
|
||||||
|
Method method = invocation.getMethod();
|
||||||
|
|
||||||
|
// Get the types and annotations associated with each parameter
|
||||||
|
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||||
|
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
|
|
||||||
|
// The Java standards require these to be parallel arrays
|
||||||
|
assert(parameterAnnotations.length == parameterTypes.length);
|
||||||
|
|
||||||
|
// Iterate through all parameters, looking for the authentication token
|
||||||
|
for (int i = 0; i < parameterTypes.length; i++) {
|
||||||
|
|
||||||
|
// Only inspect String parameters
|
||||||
|
Class<?> parameterType = parameterTypes[i];
|
||||||
|
if (parameterType != String.class)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Parameter must be declared as a REST service parameter
|
||||||
|
Annotation[] annotations = parameterAnnotations[i];
|
||||||
|
if (!isRequestParameter(annotations, "token"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The token parameter has been found - return its value
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
return (String) args[i];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// No token parameter is defined
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||||
|
|
||||||
// Get the logger for the intercepted class
|
|
||||||
Logger logger = LoggerFactory.getLogger(invocation.getMethod().getDeclaringClass());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return invocation.proceed();
|
|
||||||
|
// Invoke wrapped method
|
||||||
|
try {
|
||||||
|
return invocation.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure any associated session is invalidated if unauthorized
|
||||||
|
catch (GuacamoleUnauthorizedException e) {
|
||||||
|
|
||||||
|
// Pull authentication token from request
|
||||||
|
String token = getAuthenticationToken(invocation);
|
||||||
|
|
||||||
|
// If there is an associated auth token, invalidate it
|
||||||
|
if (authenticationService.destroyGuacamoleSession(token))
|
||||||
|
logger.debug("Implicitly invalidated session for token \"{}\".", token);
|
||||||
|
|
||||||
|
// Continue with exception processing
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional credentials are needed
|
// Additional credentials are needed
|
||||||
@@ -138,7 +259,9 @@ public class RESTExceptionWrapper implements MethodInterceptor {
|
|||||||
if (message == null)
|
if (message == null)
|
||||||
message = "Unexpected server error.";
|
message = "Unexpected server error.";
|
||||||
|
|
||||||
|
// Ensure internal errors are logged at the debug level
|
||||||
logger.debug("Unexpected exception in REST endpoint.", e);
|
logger.debug("Unexpected exception in REST endpoint.", e);
|
||||||
|
|
||||||
throw new APIException(
|
throw new APIException(
|
||||||
APIError.Type.INTERNAL_ERROR,
|
APIError.Type.INTERNAL_ERROR,
|
||||||
message
|
message
|
||||||
|
@@ -26,32 +26,61 @@ import com.google.inject.Scopes;
|
|||||||
import com.google.inject.matcher.Matchers;
|
import com.google.inject.matcher.Matchers;
|
||||||
import com.google.inject.servlet.ServletModule;
|
import com.google.inject.servlet.ServletModule;
|
||||||
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
||||||
|
import org.aopalliance.intercept.MethodInterceptor;
|
||||||
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
|
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService;
|
import org.glyptodon.guacamole.net.basic.rest.auth.TokenRESTService;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.connection.ConnectionRESTService;
|
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.connectiongroup.ConnectionGroupRESTService;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.activeconnection.ActiveConnectionRESTService;
|
import org.glyptodon.guacamole.net.basic.rest.activeconnection.ActiveConnectionRESTService;
|
||||||
|
import org.glyptodon.guacamole.net.basic.rest.auth.AuthTokenGenerator;
|
||||||
|
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
|
||||||
|
import org.glyptodon.guacamole.net.basic.rest.auth.SecureRandomAuthTokenGenerator;
|
||||||
|
import org.glyptodon.guacamole.net.basic.rest.auth.TokenSessionMap;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.history.HistoryRESTService;
|
import org.glyptodon.guacamole.net.basic.rest.history.HistoryRESTService;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.language.LanguageRESTService;
|
import org.glyptodon.guacamole.net.basic.rest.language.LanguageRESTService;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.schema.SchemaRESTService;
|
import org.glyptodon.guacamole.net.basic.rest.schema.SchemaRESTService;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.user.UserRESTService;
|
import org.glyptodon.guacamole.net.basic.rest.user.UserRESTService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Guice Module to set up the servlet mappings for the Guacamole REST API.
|
* A Guice Module to set up the servlet mappings and authentication-specific
|
||||||
|
* dependency injection for the Guacamole REST API.
|
||||||
*
|
*
|
||||||
* @author James Muehlner
|
* @author James Muehlner
|
||||||
|
* @author Michael Jumper
|
||||||
*/
|
*/
|
||||||
public class RESTServletModule extends ServletModule {
|
public class RESTServiceModule extends ServletModule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of TokenSessionMap.
|
||||||
|
*/
|
||||||
|
private final TokenSessionMap tokenSessionMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a module which handles binding of REST services and related
|
||||||
|
* authentication objects, including the singleton TokenSessionMap.
|
||||||
|
*
|
||||||
|
* @param tokenSessionMap
|
||||||
|
* An instance of TokenSessionMap to inject as a singleton wherever
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
public RESTServiceModule(TokenSessionMap tokenSessionMap) {
|
||||||
|
this.tokenSessionMap = tokenSessionMap;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureServlets() {
|
protected void configureServlets() {
|
||||||
|
|
||||||
|
// Bind session map
|
||||||
|
bind(TokenSessionMap.class).toInstance(tokenSessionMap);
|
||||||
|
|
||||||
|
// Bind low-level services
|
||||||
|
bind(AuthenticationService.class);
|
||||||
|
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
||||||
|
|
||||||
// Automatically translate GuacamoleExceptions for REST methods
|
// Automatically translate GuacamoleExceptions for REST methods
|
||||||
bindInterceptor(
|
MethodInterceptor interceptor = new RESTExceptionWrapper();
|
||||||
Matchers.any(),
|
requestInjection(interceptor);
|
||||||
new RESTMethodMatcher(),
|
bindInterceptor(Matchers.any(), new RESTMethodMatcher(), interceptor);
|
||||||
new RESTExceptionWrapper()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bind convenience services used by the REST API
|
// Bind convenience services used by the REST API
|
||||||
bind(ObjectRetrievalService.class);
|
bind(ObjectRetrievalService.class);
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2014 Glyptodon LLC
|
* Copyright (C) 2015 Glyptodon LLC
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -23,30 +23,391 @@
|
|||||||
package org.glyptodon.guacamole.net.basic.rest.auth;
|
package org.glyptodon.guacamole.net.basic.rest.auth;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.glyptodon.guacamole.GuacamoleException;
|
import org.glyptodon.guacamole.GuacamoleException;
|
||||||
|
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||||
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
import org.glyptodon.guacamole.GuacamoleUnauthorizedException;
|
||||||
|
import org.glyptodon.guacamole.environment.Environment;
|
||||||
|
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
||||||
|
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||||
|
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
import org.glyptodon.guacamole.net.auth.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.GuacamoleSession;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service for performing authentication checks in REST endpoints.
|
* A service for performing authentication checks in REST endpoints.
|
||||||
*
|
*
|
||||||
* @author James Muehlner
|
* @author James Muehlner
|
||||||
|
* @author Michael Jumper
|
||||||
*/
|
*/
|
||||||
public class AuthenticationService {
|
public class AuthenticationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Guacamole server environment.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All configured authentication providers which can be used to
|
||||||
|
* authenticate users or retrieve data associated with authenticated users.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private List<AuthenticationProvider> authProviders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map of auth tokens to sessions for the REST endpoints.
|
* The map of auth tokens to sessions for the REST endpoints.
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
private TokenSessionMap tokenSessionMap;
|
private TokenSessionMap tokenSessionMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generator for creating new auth tokens.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private AuthTokenGenerator authTokenGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches any IPv4 address.
|
||||||
|
*/
|
||||||
|
private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches any IPv6 address.
|
||||||
|
*/
|
||||||
|
private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches any IP address, regardless of version.
|
||||||
|
*/
|
||||||
|
private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern which matches valid values of the de-facto standard
|
||||||
|
* "X-Forwarded-For" header.
|
||||||
|
*/
|
||||||
|
private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a formatted string containing an IP address, or list of IP
|
||||||
|
* addresses, which represent the HTTP client and any involved proxies. As
|
||||||
|
* the headers used to determine proxies can easily be forged, this data is
|
||||||
|
* superficially validated to ensure that it at least looks like a list of
|
||||||
|
* IPs.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* The HTTP request to format.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A formatted string containing one or more IP addresses.
|
||||||
|
*/
|
||||||
|
private String getLoggableAddress(HttpServletRequest request) {
|
||||||
|
|
||||||
|
// Log X-Forwarded-For, if present and valid
|
||||||
|
String header = request.getHeader("X-Forwarded-For");
|
||||||
|
if (header != null && X_FORWARDED_FOR.matcher(header).matches())
|
||||||
|
return "[" + header + ", " + request.getRemoteAddr() + "]";
|
||||||
|
|
||||||
|
// If header absent or invalid, just use source IP
|
||||||
|
return request.getRemoteAddr();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts authentication against all AuthenticationProviders, in order,
|
||||||
|
* using the provided credentials. The first authentication failure takes
|
||||||
|
* priority, but remaining AuthenticationProviders are attempted. If any
|
||||||
|
* AuthenticationProvider succeeds, the resulting AuthenticatedUser is
|
||||||
|
* returned, and no further AuthenticationProviders are tried.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials to use for authentication.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The AuthenticatedUser given by the highest-priority
|
||||||
|
* AuthenticationProvider for which the given credentials are valid.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the given credentials are not valid for any
|
||||||
|
* AuthenticationProvider, or if an error occurs while authenticating
|
||||||
|
* the user.
|
||||||
|
*/
|
||||||
|
private AuthenticatedUser authenticateUser(Credentials credentials)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
GuacamoleCredentialsException authFailure = null;
|
||||||
|
|
||||||
|
// Attempt authentication against each AuthenticationProvider
|
||||||
|
for (AuthenticationProvider authProvider : authProviders) {
|
||||||
|
|
||||||
|
// Attempt authentication
|
||||||
|
try {
|
||||||
|
AuthenticatedUser authenticatedUser = authProvider.authenticateUser(credentials);
|
||||||
|
if (authenticatedUser != null)
|
||||||
|
return authenticatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First failure takes priority for now
|
||||||
|
catch (GuacamoleCredentialsException e) {
|
||||||
|
if (authFailure == null)
|
||||||
|
authFailure = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a specific failure occured, rethrow that
|
||||||
|
if (authFailure != null)
|
||||||
|
throw authFailure;
|
||||||
|
|
||||||
|
// Otherwise, request standard username/password
|
||||||
|
throw new GuacamoleInvalidCredentialsException(
|
||||||
|
"Permission Denied.",
|
||||||
|
CredentialsInfo.USERNAME_PASSWORD
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-authenticates the given AuthenticatedUser against the
|
||||||
|
* AuthenticationProvider that originally created it, using the given
|
||||||
|
* Credentials.
|
||||||
|
*
|
||||||
|
* @param authenticatedUser
|
||||||
|
* The AuthenticatedUser to re-authenticate.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The Credentials to use to re-authenticate the user.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A AuthenticatedUser which may have been updated due to re-
|
||||||
|
* authentication.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error prevents the user from being re-authenticated.
|
||||||
|
*/
|
||||||
|
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||||
|
Credentials credentials) throws GuacamoleException {
|
||||||
|
|
||||||
|
// Get original AuthenticationProvider
|
||||||
|
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
||||||
|
|
||||||
|
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
||||||
|
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||||
|
if (authenticatedUser == null)
|
||||||
|
throw new GuacamoleSecurityException("User re-authentication failed.");
|
||||||
|
|
||||||
|
return authenticatedUser;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the AuthenticatedUser associated with the given session and
|
||||||
|
* credentials, performing a fresh authentication and creating a new
|
||||||
|
* AuthenticatedUser if necessary.
|
||||||
|
*
|
||||||
|
* @param existingSession
|
||||||
|
* The current GuacamoleSession, or null if no session exists yet.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The Credentials to use to authenticate the user.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The AuthenticatedUser associated with the given session and
|
||||||
|
* credentials.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while authenticating or re-authenticating the
|
||||||
|
* user.
|
||||||
|
*/
|
||||||
|
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
||||||
|
Credentials credentials) throws GuacamoleException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Re-authenticate user if session exists
|
||||||
|
if (existingSession != null)
|
||||||
|
return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
||||||
|
|
||||||
|
// Otherwise, attempt authentication as a new user
|
||||||
|
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||||
|
if (logger.isInfoEnabled())
|
||||||
|
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||||
|
authenticatedUser.getIdentifier(),
|
||||||
|
getLoggableAddress(credentials.getRequest()));
|
||||||
|
|
||||||
|
return authenticatedUser;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log and rethrow any authentication errors
|
||||||
|
catch (GuacamoleException e) {
|
||||||
|
|
||||||
|
// Get request and username for sake of logging
|
||||||
|
HttpServletRequest request = credentials.getRequest();
|
||||||
|
String username = credentials.getUsername();
|
||||||
|
|
||||||
|
// Log authentication failures with associated usernames
|
||||||
|
if (username != null) {
|
||||||
|
if (logger.isWarnEnabled())
|
||||||
|
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
||||||
|
getLoggableAddress(request), username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log anonymous authentication failures
|
||||||
|
else if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Anonymous authentication attempt from {} failed.",
|
||||||
|
getLoggableAddress(request));
|
||||||
|
|
||||||
|
// Rethrow exception
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all UserContexts associated with the given AuthenticatedUser,
|
||||||
|
* updating existing UserContexts, if any. If no UserContexts are yet
|
||||||
|
* associated with the given AuthenticatedUser, new UserContexts are
|
||||||
|
* generated by polling each available AuthenticationProvider.
|
||||||
|
*
|
||||||
|
* @param existingSession
|
||||||
|
* The current GuacamoleSession, or null if no session exists yet.
|
||||||
|
*
|
||||||
|
* @param authenticatedUser
|
||||||
|
* The AuthenticatedUser that has successfully authenticated or re-
|
||||||
|
* authenticated.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* A List of all UserContexts associated with the given
|
||||||
|
* AuthenticatedUser.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If an error occurs while creating or updating any UserContext.
|
||||||
|
*/
|
||||||
|
private List<UserContext> getUserContexts(GuacamoleSession existingSession,
|
||||||
|
AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||||
|
|
||||||
|
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
|
||||||
|
|
||||||
|
// If UserContexts already exist, update them and add to the list
|
||||||
|
if (existingSession != null) {
|
||||||
|
|
||||||
|
// Update all old user contexts
|
||||||
|
List<UserContext> oldUserContexts = existingSession.getUserContexts();
|
||||||
|
for (UserContext oldUserContext : oldUserContexts) {
|
||||||
|
|
||||||
|
// Update existing UserContext
|
||||||
|
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
||||||
|
UserContext userContext = authProvider.updateUserContext(oldUserContext, authenticatedUser);
|
||||||
|
|
||||||
|
// Add to available data, if successful
|
||||||
|
if (userContext != null)
|
||||||
|
userContexts.add(userContext);
|
||||||
|
|
||||||
|
// If unsuccessful, log that this happened, as it may be a bug
|
||||||
|
else
|
||||||
|
logger.debug("AuthenticationProvider \"{}\" retroactively destroyed its UserContext.",
|
||||||
|
authProvider.getClass().getName());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, create new UserContexts from available AuthenticationProviders
|
||||||
|
else {
|
||||||
|
|
||||||
|
// Get UserContexts from each available AuthenticationProvider
|
||||||
|
for (AuthenticationProvider authProvider : authProviders) {
|
||||||
|
|
||||||
|
// Generate new UserContext
|
||||||
|
UserContext userContext = authProvider.getUserContext(authenticatedUser);
|
||||||
|
|
||||||
|
// Add to available data, if successful
|
||||||
|
if (userContext != null)
|
||||||
|
userContexts.add(userContext);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return userContexts;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates a user using the given credentials and optional
|
||||||
|
* authentication token, returning the authentication token associated with
|
||||||
|
* the user's Guacamole session, which may be newly generated. If an
|
||||||
|
* existing token is provided, the authentication procedure will attempt to
|
||||||
|
* update or reuse the provided token, but it is possible that a new token
|
||||||
|
* will be returned. Note that this function CANNOT return null.
|
||||||
|
*
|
||||||
|
* @param credentials
|
||||||
|
* The credentials to use when authenticating the user.
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* The authentication token to use if attempting to re-authenticate an
|
||||||
|
* existing session, or null to request a new token.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The authentication token associated with the newly created or
|
||||||
|
* existing session.
|
||||||
|
*
|
||||||
|
* @throws GuacamoleException
|
||||||
|
* If the authentication or re-authentication attempt fails.
|
||||||
|
*/
|
||||||
|
public String authenticate(Credentials credentials, String token)
|
||||||
|
throws GuacamoleException {
|
||||||
|
|
||||||
|
// Pull existing session if token provided
|
||||||
|
GuacamoleSession existingSession;
|
||||||
|
if (token != null)
|
||||||
|
existingSession = tokenSessionMap.get(token);
|
||||||
|
else
|
||||||
|
existingSession = null;
|
||||||
|
|
||||||
|
// Get up-to-date AuthenticatedUser and associated UserContexts
|
||||||
|
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
||||||
|
List<UserContext> userContexts = getUserContexts(existingSession, authenticatedUser);
|
||||||
|
|
||||||
|
// Update existing session, if it exists
|
||||||
|
String authToken;
|
||||||
|
if (existingSession != null) {
|
||||||
|
authToken = token;
|
||||||
|
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||||
|
existingSession.setUserContexts(userContexts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no existing session, generate a new token/session pair
|
||||||
|
else {
|
||||||
|
authToken = authTokenGenerator.getToken();
|
||||||
|
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||||
|
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
return authToken;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the Guacamole session for a given auth token, if the auth token
|
* Finds the Guacamole session for a given auth token, if the auth token
|
||||||
* represents a currently logged in user. Throws an unauthorized error
|
* represents a currently logged in user. Throws an unauthorized error
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*
|
*
|
||||||
* @param authToken The auth token to check against the map of logged in users.
|
* @param authToken The auth token to check against the map of logged in users.
|
||||||
* @return The session that corresponds to the provided auth token.
|
* @return The session that corresponds to the provided auth token.
|
||||||
* @throws GuacamoleException If the auth token does not correspond to any
|
* @throws GuacamoleException If the auth token does not correspond to any
|
||||||
@@ -66,6 +427,32 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates a specific authentication token and its corresponding
|
||||||
|
* Guacamole session, effectively logging out the associated user. If the
|
||||||
|
* authentication token is not valid, this function has no effect.
|
||||||
|
*
|
||||||
|
* @param authToken
|
||||||
|
* The token being invalidated.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* true if the given authentication token was valid and the
|
||||||
|
* corresponding Guacamole session was destroyed, false if the given
|
||||||
|
* authentication token was not valid and no action was taken.
|
||||||
|
*/
|
||||||
|
public boolean destroyGuacamoleSession(String authToken) {
|
||||||
|
|
||||||
|
// Remove corresponding GuacamoleSession if the token is valid
|
||||||
|
GuacamoleSession session = tokenSessionMap.remove(authToken);
|
||||||
|
if (session == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Invalidate the removed session
|
||||||
|
session.invalidate();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all UserContexts associated with a given auth token, if the auth
|
* Returns all UserContexts associated with a given auth token, if the auth
|
||||||
* token represents a currently logged in user. Throws an unauthorized
|
* token represents a currently logged in user. Throws an unauthorized
|
||||||
|
@@ -26,7 +26,6 @@ import com.google.inject.Inject;
|
|||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.ws.rs.DELETE;
|
import javax.ws.rs.DELETE;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
@@ -40,15 +39,9 @@ import javax.ws.rs.core.MultivaluedMap;
|
|||||||
import javax.xml.bind.DatatypeConverter;
|
import javax.xml.bind.DatatypeConverter;
|
||||||
import org.glyptodon.guacamole.GuacamoleException;
|
import org.glyptodon.guacamole.GuacamoleException;
|
||||||
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
|
||||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
|
||||||
import org.glyptodon.guacamole.environment.Environment;
|
|
||||||
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
||||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
|
||||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
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.GuacamoleSession;
|
||||||
import org.glyptodon.guacamole.net.basic.rest.APIRequest;
|
import org.glyptodon.guacamole.net.basic.rest.APIRequest;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -64,81 +57,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class TokenRESTService {
|
public class TokenRESTService {
|
||||||
|
|
||||||
/**
|
|
||||||
* The Guacamole server environment.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private Environment environment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All configured authentication providers which can be used to
|
|
||||||
* authenticate users or retrieve data associated with authenticated users.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private List<AuthenticationProvider> authProviders;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The map of auth tokens to sessions for the REST endpoints.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private TokenSessionMap tokenSessionMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generator for creating new auth tokens.
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
private AuthTokenGenerator authTokenGenerator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger for this class.
|
* Logger for this class.
|
||||||
*/
|
*/
|
||||||
private static final Logger logger = LoggerFactory.getLogger(TokenRESTService.class);
|
private static final Logger logger = LoggerFactory.getLogger(TokenRESTService.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression which matches any IPv4 address.
|
* Service for authenticating users and managing their Guacamole sessions.
|
||||||
*/
|
*/
|
||||||
private static final String IPV4_ADDRESS_REGEX = "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})";
|
@Inject
|
||||||
|
private AuthenticationService authenticationService;
|
||||||
/**
|
|
||||||
* Regular expression which matches any IPv6 address.
|
|
||||||
*/
|
|
||||||
private static final String IPV6_ADDRESS_REGEX = "([0-9a-fA-F]*(:[0-9a-fA-F]*){0,7})";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regular expression which matches any IP address, regardless of version.
|
|
||||||
*/
|
|
||||||
private static final String IP_ADDRESS_REGEX = "(" + IPV4_ADDRESS_REGEX + "|" + IPV6_ADDRESS_REGEX + ")";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pattern which matches valid values of the de-facto standard
|
|
||||||
* "X-Forwarded-For" header.
|
|
||||||
*/
|
|
||||||
private static final Pattern X_FORWARDED_FOR = Pattern.compile("^" + IP_ADDRESS_REGEX + "(, " + IP_ADDRESS_REGEX + ")*$");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a formatted string containing an IP address, or list of IP
|
|
||||||
* addresses, which represent the HTTP client and any involved proxies. As
|
|
||||||
* the headers used to determine proxies can easily be forged, this data is
|
|
||||||
* superficially validated to ensure that it at least looks like a list of
|
|
||||||
* IPs.
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* The HTTP request to format.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A formatted string containing one or more IP addresses.
|
|
||||||
*/
|
|
||||||
private String getLoggableAddress(HttpServletRequest request) {
|
|
||||||
|
|
||||||
// Log X-Forwarded-For, if present and valid
|
|
||||||
String header = request.getHeader("X-Forwarded-For");
|
|
||||||
if (header != null && X_FORWARDED_FOR.matcher(header).matches())
|
|
||||||
return "[" + header + ", " + request.getRemoteAddr() + "]";
|
|
||||||
|
|
||||||
// If header absent or invalid, just use source IP
|
|
||||||
return request.getRemoteAddr();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the credentials associated with the given request, using the
|
* Returns the credentials associated with the given request, using the
|
||||||
@@ -205,228 +133,6 @@ public class TokenRESTService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts authentication against all AuthenticationProviders, in order,
|
|
||||||
* using the provided credentials. The first authentication failure takes
|
|
||||||
* priority, but remaining AuthenticationProviders are attempted. If any
|
|
||||||
* AuthenticationProvider succeeds, the resulting AuthenticatedUser is
|
|
||||||
* returned, and no further AuthenticationProviders are tried.
|
|
||||||
*
|
|
||||||
* @param credentials
|
|
||||||
* The credentials to use for authentication.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The AuthenticatedUser given by the highest-priority
|
|
||||||
* AuthenticationProvider for which the given credentials are valid.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If the given credentials are not valid for any
|
|
||||||
* AuthenticationProvider, or if an error occurs while authenticating
|
|
||||||
* the user.
|
|
||||||
*/
|
|
||||||
private AuthenticatedUser authenticateUser(Credentials credentials)
|
|
||||||
throws GuacamoleException {
|
|
||||||
|
|
||||||
GuacamoleCredentialsException authFailure = null;
|
|
||||||
|
|
||||||
// Attempt authentication against each AuthenticationProvider
|
|
||||||
for (AuthenticationProvider authProvider : authProviders) {
|
|
||||||
|
|
||||||
// Attempt authentication
|
|
||||||
try {
|
|
||||||
AuthenticatedUser authenticatedUser = authProvider.authenticateUser(credentials);
|
|
||||||
if (authenticatedUser != null)
|
|
||||||
return authenticatedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First failure takes priority for now
|
|
||||||
catch (GuacamoleCredentialsException e) {
|
|
||||||
if (authFailure == null)
|
|
||||||
authFailure = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a specific failure occured, rethrow that
|
|
||||||
if (authFailure != null)
|
|
||||||
throw authFailure;
|
|
||||||
|
|
||||||
// Otherwise, request standard username/password
|
|
||||||
throw new GuacamoleInvalidCredentialsException(
|
|
||||||
"Permission Denied.",
|
|
||||||
CredentialsInfo.USERNAME_PASSWORD
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-authenticates the given AuthenticatedUser against the
|
|
||||||
* AuthenticationProvider that originally created it, using the given
|
|
||||||
* Credentials.
|
|
||||||
*
|
|
||||||
* @param authenticatedUser
|
|
||||||
* The AuthenticatedUser to re-authenticate.
|
|
||||||
*
|
|
||||||
* @param credentials
|
|
||||||
* The Credentials to use to re-authenticate the user.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A AuthenticatedUser which may have been updated due to re-
|
|
||||||
* authentication.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If an error prevents the user from being re-authenticated.
|
|
||||||
*/
|
|
||||||
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
|
||||||
Credentials credentials) throws GuacamoleException {
|
|
||||||
|
|
||||||
// Get original AuthenticationProvider
|
|
||||||
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
|
||||||
|
|
||||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
|
||||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
|
||||||
if (authenticatedUser == null)
|
|
||||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
|
||||||
|
|
||||||
return authenticatedUser;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the AuthenticatedUser associated with the given session and
|
|
||||||
* credentials, performing a fresh authentication and creating a new
|
|
||||||
* AuthenticatedUser if necessary.
|
|
||||||
*
|
|
||||||
* @param existingSession
|
|
||||||
* The current GuacamoleSession, or null if no session exists yet.
|
|
||||||
*
|
|
||||||
* @param credentials
|
|
||||||
* The Credentials to use to authenticate the user.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The AuthenticatedUser associated with the given session and
|
|
||||||
* credentials.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If an error occurs while authenticating or re-authenticating the
|
|
||||||
* user.
|
|
||||||
*/
|
|
||||||
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
|
||||||
Credentials credentials) throws GuacamoleException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// Re-authenticate user if session exists
|
|
||||||
if (existingSession != null)
|
|
||||||
return updateAuthenticatedUser(existingSession.getAuthenticatedUser(), credentials);
|
|
||||||
|
|
||||||
// Otherwise, attempt authentication as a new user
|
|
||||||
AuthenticatedUser authenticatedUser = authenticateUser(credentials);
|
|
||||||
if (logger.isInfoEnabled())
|
|
||||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
|
||||||
authenticatedUser.getIdentifier(),
|
|
||||||
getLoggableAddress(credentials.getRequest()));
|
|
||||||
|
|
||||||
return authenticatedUser;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log and rethrow any authentication errors
|
|
||||||
catch (GuacamoleException e) {
|
|
||||||
|
|
||||||
// Get request and username for sake of logging
|
|
||||||
HttpServletRequest request = credentials.getRequest();
|
|
||||||
String username = credentials.getUsername();
|
|
||||||
|
|
||||||
// Log authentication failures with associated usernames
|
|
||||||
if (username != null) {
|
|
||||||
if (logger.isWarnEnabled())
|
|
||||||
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
|
||||||
getLoggableAddress(request), username);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log anonymous authentication failures
|
|
||||||
else if (logger.isDebugEnabled())
|
|
||||||
logger.debug("Anonymous authentication attempt from {} failed.",
|
|
||||||
getLoggableAddress(request));
|
|
||||||
|
|
||||||
// Rethrow exception
|
|
||||||
throw e;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all UserContexts associated with the given AuthenticatedUser,
|
|
||||||
* updating existing UserContexts, if any. If no UserContexts are yet
|
|
||||||
* associated with the given AuthenticatedUser, new UserContexts are
|
|
||||||
* generated by polling each available AuthenticationProvider.
|
|
||||||
*
|
|
||||||
* @param existingSession
|
|
||||||
* The current GuacamoleSession, or null if no session exists yet.
|
|
||||||
*
|
|
||||||
* @param authenticatedUser
|
|
||||||
* The AuthenticatedUser that has successfully authenticated or re-
|
|
||||||
* authenticated.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A List of all UserContexts associated with the given
|
|
||||||
* AuthenticatedUser.
|
|
||||||
*
|
|
||||||
* @throws GuacamoleException
|
|
||||||
* If an error occurs while creating or updating any UserContext.
|
|
||||||
*/
|
|
||||||
private List<UserContext> getUserContexts(GuacamoleSession existingSession,
|
|
||||||
AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
|
||||||
|
|
||||||
List<UserContext> userContexts = new ArrayList<UserContext>(authProviders.size());
|
|
||||||
|
|
||||||
// If UserContexts already exist, update them and add to the list
|
|
||||||
if (existingSession != null) {
|
|
||||||
|
|
||||||
// Update all old user contexts
|
|
||||||
List<UserContext> oldUserContexts = existingSession.getUserContexts();
|
|
||||||
for (UserContext oldUserContext : oldUserContexts) {
|
|
||||||
|
|
||||||
// Update existing UserContext
|
|
||||||
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
|
||||||
UserContext userContext = authProvider.updateUserContext(oldUserContext, authenticatedUser);
|
|
||||||
|
|
||||||
// Add to available data, if successful
|
|
||||||
if (userContext != null)
|
|
||||||
userContexts.add(userContext);
|
|
||||||
|
|
||||||
// If unsuccessful, log that this happened, as it may be a bug
|
|
||||||
else
|
|
||||||
logger.debug("AuthenticationProvider \"{}\" retroactively destroyed its UserContext.",
|
|
||||||
authProvider.getClass().getName());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, create new UserContexts from available AuthenticationProviders
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Get UserContexts from each available AuthenticationProvider
|
|
||||||
for (AuthenticationProvider authProvider : authProviders) {
|
|
||||||
|
|
||||||
// Generate new UserContext
|
|
||||||
UserContext userContext = authProvider.getUserContext(authenticatedUser);
|
|
||||||
|
|
||||||
// Add to available data, if successful
|
|
||||||
if (userContext != null)
|
|
||||||
userContexts.add(userContext);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return userContexts;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates a user, generates an auth token, associates that auth token
|
* Authenticates a user, generates an auth token, associates that auth token
|
||||||
* with the user's UserContext for use by further requests. If an existing
|
* with the user's UserContext for use by further requests. If an existing
|
||||||
@@ -472,43 +178,27 @@ public class TokenRESTService {
|
|||||||
// Reconstitute the HTTP request with the map of parameters
|
// Reconstitute the HTTP request with the map of parameters
|
||||||
HttpServletRequest request = new APIRequest(consumedRequest, 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
|
// Build credentials from request
|
||||||
Credentials credentials = getCredentials(request, username, password);
|
Credentials credentials = getCredentials(request, username, password);
|
||||||
|
|
||||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
// Create/update session producing possibly-new token
|
||||||
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
token = authenticationService.authenticate(credentials, token);
|
||||||
List<UserContext> userContexts = getUserContexts(existingSession, authenticatedUser);
|
|
||||||
|
|
||||||
// Update existing session, if it exists
|
// Pull corresponding session
|
||||||
String authToken;
|
GuacamoleSession session = authenticationService.getGuacamoleSession(token);
|
||||||
if (existingSession != null) {
|
if (session == null)
|
||||||
authToken = token;
|
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
|
||||||
existingSession.setUserContexts(userContexts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no existing session, generate a new token/session pair
|
|
||||||
else {
|
|
||||||
authToken = authTokenGenerator.getToken();
|
|
||||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
|
||||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build list of all available auth providers
|
// Build list of all available auth providers
|
||||||
|
List<UserContext> userContexts = session.getUserContexts();
|
||||||
List<String> authProviderIdentifiers = new ArrayList<String>(userContexts.size());
|
List<String> authProviderIdentifiers = new ArrayList<String>(userContexts.size());
|
||||||
for (UserContext userContext : userContexts)
|
for (UserContext userContext : userContexts)
|
||||||
authProviderIdentifiers.add(userContext.getAuthenticationProvider().getIdentifier());
|
authProviderIdentifiers.add(userContext.getAuthenticationProvider().getIdentifier());
|
||||||
|
|
||||||
// Return possibly-new auth token
|
// Return possibly-new auth token
|
||||||
|
AuthenticatedUser authenticatedUser = session.getAuthenticatedUser();
|
||||||
return new APIAuthenticationResult(
|
return new APIAuthenticationResult(
|
||||||
authToken,
|
token,
|
||||||
authenticatedUser.getIdentifier(),
|
authenticatedUser.getIdentifier(),
|
||||||
authenticatedUser.getAuthenticationProvider().getIdentifier(),
|
authenticatedUser.getAuthenticationProvider().getIdentifier(),
|
||||||
authProviderIdentifiers
|
authProviderIdentifiers
|
||||||
@@ -530,12 +220,10 @@ public class TokenRESTService {
|
|||||||
@Path("/{token}")
|
@Path("/{token}")
|
||||||
public void invalidateToken(@PathParam("token") String authToken)
|
public void invalidateToken(@PathParam("token") String authToken)
|
||||||
throws GuacamoleException {
|
throws GuacamoleException {
|
||||||
|
|
||||||
GuacamoleSession session = tokenSessionMap.remove(authToken);
|
|
||||||
if (session == null)
|
|
||||||
throw new GuacamoleResourceNotFoundException("No such token.");
|
|
||||||
|
|
||||||
session.invalidate();
|
// Invalidate session, if it exists
|
||||||
|
if (!authenticationService.destroyGuacamoleSession(authToken))
|
||||||
|
throw new GuacamoleResourceNotFoundException("No such token.");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,11 +33,12 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
var ScrollState = $injector.get('ScrollState');
|
var ScrollState = $injector.get('ScrollState');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
var $location = $injector.get('$location');
|
var $location = $injector.get('$location');
|
||||||
var guacClientManager = $injector.get('guacClientManager');
|
var authenticationService = $injector.get('authenticationService');
|
||||||
var guacNotification = $injector.get('guacNotification');
|
var guacClientManager = $injector.get('guacClientManager');
|
||||||
var preferenceService = $injector.get('preferenceService');
|
var guacNotification = $injector.get('guacNotification');
|
||||||
var userPageService = $injector.get('userPageService');
|
var preferenceService = $injector.get('preferenceService');
|
||||||
|
var userPageService = $injector.get('userPageService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum number of pixels a drag gesture must move to result in the
|
* The minimum number of pixels a drag gesture must move to result in the
|
||||||
@@ -409,6 +410,30 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
$scope.page.title = name;
|
$scope.page.title = name;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a notification at the end of a Guacamole connection, whether
|
||||||
|
* that connection is ending normally or due to an error. As the end of
|
||||||
|
* a Guacamole connection may be due to changes in authentication status,
|
||||||
|
* this will also implicitly peform a re-authentication attempt to check
|
||||||
|
* for such changes, possibly resulting in auth-related events like
|
||||||
|
* guacInvalidCredentials.
|
||||||
|
*
|
||||||
|
* @param {Notification|Boolean|Object} status
|
||||||
|
* The status notification to show, as would be accepted by
|
||||||
|
* guacNotification.showStatus().
|
||||||
|
*/
|
||||||
|
var notifyConnectionClosed = function notifyConnectionClosed(status) {
|
||||||
|
|
||||||
|
// Re-authenticate to verify auth status at end of connection
|
||||||
|
authenticationService.updateCurrentToken($location.search())
|
||||||
|
|
||||||
|
// If authentication is OK, show the requested status
|
||||||
|
.then(function authenticationCheckSuccessful() {
|
||||||
|
guacNotification.showStatus(status);
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
// Show status dialog when connection status changes
|
// Show status dialog when connection status changes
|
||||||
$scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) {
|
$scope.$watch('client.clientState.connectionState', function clientStateChanged(connectionState) {
|
||||||
|
|
||||||
@@ -448,7 +473,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
var countdown = (status in CLIENT_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null;
|
var countdown = (status in CLIENT_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null;
|
||||||
|
|
||||||
// Show error status
|
// Show error status
|
||||||
guacNotification.showStatus({
|
notifyConnectionClosed({
|
||||||
className : "error",
|
className : "error",
|
||||||
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
||||||
text : "CLIENT.ERROR_CLIENT_" + errorName,
|
text : "CLIENT.ERROR_CLIENT_" + errorName,
|
||||||
@@ -468,7 +493,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
var countdown = (status in TUNNEL_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null;
|
var countdown = (status in TUNNEL_AUTO_RECONNECT) ? RECONNECT_COUNTDOWN : null;
|
||||||
|
|
||||||
// Show error status
|
// Show error status
|
||||||
guacNotification.showStatus({
|
notifyConnectionClosed({
|
||||||
className : "error",
|
className : "error",
|
||||||
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
title : "CLIENT.DIALOG_HEADER_CONNECTION_ERROR",
|
||||||
text : "CLIENT.ERROR_TUNNEL_" + errorName,
|
text : "CLIENT.ERROR_TUNNEL_" + errorName,
|
||||||
@@ -480,7 +505,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
|
|
||||||
// Disconnected
|
// Disconnected
|
||||||
else if (connectionState === ManagedClientState.ConnectionState.DISCONNECTED) {
|
else if (connectionState === ManagedClientState.ConnectionState.DISCONNECTED) {
|
||||||
guacNotification.showStatus({
|
notifyConnectionClosed({
|
||||||
title : "CLIENT.DIALOG_HEADER_DISCONNECTED",
|
title : "CLIENT.DIALOG_HEADER_DISCONNECTED",
|
||||||
text : "CLIENT.TEXT_CLIENT_STATUS_" + connectionState.toUpperCase(),
|
text : "CLIENT.TEXT_CLIENT_STATUS_" + connectionState.toUpperCase(),
|
||||||
actions : actions
|
actions : actions
|
||||||
|
Reference in New Issue
Block a user