mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-566: Replace the entire RESTExceptionWrapper with the ExceptionMapper implementation.
This commit is contained in:
@@ -39,7 +39,6 @@ import org.apache.guacamole.resource.Resource;
|
||||
import org.apache.guacamole.resource.ResourceServlet;
|
||||
import org.apache.guacamole.resource.SequenceResource;
|
||||
import org.apache.guacamole.resource.WebApplicationResource;
|
||||
import org.apache.guacamole.rest.GuacamoleExceptionMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -418,10 +417,7 @@ public class ExtensionModule extends ServletModule {
|
||||
// Bind resource services
|
||||
bind(LanguageResourceService.class).toInstance(languageResourceService);
|
||||
bind(PatchResourceService.class).toInstance(patchResourceService);
|
||||
|
||||
// Get ExceptionMapper to rewrite exceptions in JSON.
|
||||
bind(GuacamoleExceptionMapper.class);
|
||||
|
||||
|
||||
// Load initial language resources from servlet context
|
||||
languageResourceService.addLanguageResources(getServletContext());
|
||||
|
||||
|
@@ -19,12 +19,23 @@
|
||||
|
||||
package org.apache.guacamole.rest;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.apache.guacamole.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -37,12 +48,59 @@ import org.slf4j.LoggerFactory;
|
||||
@Singleton
|
||||
public class GuacamoleExceptionMapper
|
||||
implements ExceptionMapper<GuacamoleException> {
|
||||
|
||||
|
||||
/**
|
||||
* The logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(GuacamoleExceptionMapper.class);
|
||||
|
||||
/**
|
||||
* The request associated with this instance of this mapper.
|
||||
*/
|
||||
@Context private HttpServletRequest request;
|
||||
|
||||
/**
|
||||
* The authentication service associated with the currently active session.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
/**
|
||||
* Returns the authentication token that is in use in the current session,
|
||||
* if present, or null if otherwise.
|
||||
*
|
||||
* @return
|
||||
* The authentication token for the current session, or null if no
|
||||
* token is present.
|
||||
*/
|
||||
private String getAuthenticationToken() {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
|
||||
for (String paramName : parameters.keySet()) {
|
||||
if (paramName.equals("token")) {
|
||||
String tokenParams[] = parameters.get(paramName);
|
||||
if (tokenParams[0] != null && !tokenParams[0].isEmpty())
|
||||
return tokenParams[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response toResponse(GuacamoleException e) {
|
||||
logger.debug(">>>EXMAPPER<<< Mapping exception {}", e.getMessage());
|
||||
|
||||
if (e instanceof GuacamoleUnauthorizedException) {
|
||||
String token = getAuthenticationToken();
|
||||
|
||||
if (authenticationService.destroyGuacamoleSession(token))
|
||||
logger.debug("Implicitly invalidated session for token \"{}\"", token);
|
||||
}
|
||||
|
||||
return Response
|
||||
.status(e.getHttpStatusCode())
|
||||
.entity(new APIError(e))
|
||||
|
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.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 javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleUnauthorizedException;
|
||||
import org.apache.guacamole.rest.auth.AuthenticationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A method interceptor which wraps custom exception handling around methods
|
||||
* which can throw GuacamoleExceptions and which are exposed through the REST
|
||||
* interface. The various types of GuacamoleExceptions are automatically
|
||||
* translated into appropriate HTTP responses, including JSON describing the
|
||||
* error that occurred.
|
||||
*/
|
||||
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
|
||||
public Object invoke(MethodInvocation invocation) throws WebApplicationException {
|
||||
|
||||
try {
|
||||
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Translate GuacamoleException subclasses to HTTP error codes
|
||||
catch (GuacamoleException e) {
|
||||
throw new APIException(
|
||||
Response.Status.fromStatusCode(e.getHttpStatusCode()),
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Rethrow unchecked exceptions such that they are properly wrapped
|
||||
catch (Throwable t) {
|
||||
|
||||
// Log all reasonable details of error
|
||||
String message = t.getMessage();
|
||||
if (message != null)
|
||||
logger.error("Unexpected internal error: {}", message);
|
||||
else
|
||||
logger.error("An internal error occurred, but did not contain "
|
||||
+ "an error message. Enable debug-level logging for "
|
||||
+ "details.");
|
||||
|
||||
// Ensure internal errors are fully logged at the debug level
|
||||
logger.debug("Unexpected error in REST endpoint.", t);
|
||||
|
||||
throw new APIException(Response.Status.INTERNAL_SERVER_ERROR,
|
||||
new GuacamoleException("Unexpected internal error.", t));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -83,11 +83,6 @@ public class RESTServiceModule extends ServletModule {
|
||||
bind(AuthenticationService.class);
|
||||
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
|
||||
bind(DecorationService.class);
|
||||
|
||||
// Automatically translate GuacamoleExceptions for REST methods
|
||||
MethodInterceptor interceptor = new RESTExceptionWrapper();
|
||||
requestInjection(interceptor);
|
||||
bindInterceptor(Matchers.any(), new RESTMethodMatcher(), interceptor);
|
||||
|
||||
// Get the ExceptionMapper that will rewrite exceptions into JSON.
|
||||
bind(GuacamoleExceptionMapper.class);
|
||||
|
Reference in New Issue
Block a user