From eb91f4d87a408ff82a308fe9f45069771d5ba1aa Mon Sep 17 00:00:00 2001 From: Nick Couchman Date: Tue, 29 May 2018 17:24:54 -0400 Subject: [PATCH] GUACAMOLE-566: Replace the entire RESTExceptionWrapper with the ExceptionMapper implementation. --- .../guacamole/extension/ExtensionModule.java | 6 +- .../rest/GuacamoleExceptionMapper.java | 60 +++++- .../guacamole/rest/RESTExceptionWrapper.java | 200 ------------------ .../guacamole/rest/RESTServiceModule.java | 5 - 4 files changed, 60 insertions(+), 211 deletions(-) delete mode 100644 guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java diff --git a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java index 665c02881..822741c1a 100644 --- a/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/extension/ExtensionModule.java @@ -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()); diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/GuacamoleExceptionMapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/GuacamoleExceptionMapper.java index 4af39b8fa..f27c59c07 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/GuacamoleExceptionMapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/GuacamoleExceptionMapper.java @@ -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 { - + + /** + * 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 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)) diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java deleted file mode 100644 index fb854b041..000000000 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java +++ /dev/null @@ -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 - * @QueryParam or @FormParam 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)); - - } - - } - -} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java index cfa8b063a..8075b1e68 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTServiceModule.java @@ -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);