diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java b/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java index 9a390a872..c6a2c635c 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/APIError.java @@ -36,6 +36,11 @@ public class APIError { */ private final String message; + /** + * The associated Guacamole protocol status code. + */ + private final Integer statusCode; + /** * All expected request parameters, if any, as a collection of fields. */ @@ -81,7 +86,14 @@ public class APIError { /** * Permission was denied to perform the requested operation. */ - PERMISSION_DENIED(Response.Status.FORBIDDEN); + PERMISSION_DENIED(Response.Status.FORBIDDEN), + + /** + * An error occurred within an intercepted stream, terminating that + * stream. The Guacamole protocol status code of that error can be + * retrieved with getStatusCode(). + */ + STREAM_ERROR(Response.Status.BAD_REQUEST); /** * The HTTP status associated with this error type. @@ -110,6 +122,27 @@ public class APIError { } + /** + * Creates a new APIError of type STREAM_ERROR and having the given + * Guacamole protocol status code and human-readable message. The status + * code and message should be taken directly from the "ack" instruction + * causing the error. + * + * @param statusCode + * The Guacamole protocol status code describing the error that + * occurred within the intercepted stream. + * + * @param message + * An arbitrary human-readable message describing the error that + * occurred. + */ + public APIError(int statusCode, String message) { + this.type = Type.STREAM_ERROR; + this.message = message; + this.statusCode = statusCode; + this.expected = null; + } + /** * Create a new APIError with the specified error message. * @@ -120,9 +153,10 @@ public class APIError { * The error message. */ public APIError(Type type, String message) { - this.type = type; - this.message = message; - this.expected = null; + this.type = type; + this.message = message; + this.statusCode = null; + this.expected = null; } /** @@ -140,9 +174,10 @@ public class APIError { * a result of the original request, as a collection of fields. */ public APIError(Type type, String message, Collection expected) { - this.type = type; - this.message = message; - this.expected = expected; + this.type = type; + this.message = message; + this.statusCode = null; + this.expected = expected; } /** @@ -155,6 +190,19 @@ public class APIError { return type; } + /** + * Returns the Guacamole protocol status code associated with the error + * that occurred. This is only valid for errors of type STREAM_ERROR. + * + * @return + * The Guacamole protocol status code associated with the error that + * occurred. If the error is not of type STREAM_ERROR, this will be + * null. + */ + public Integer getStatusCode() { + return statusCode; + } + /** * Returns a collection of all required parameters, where each parameter is * represented by a field. diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java b/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java index 752dd0f84..0a004bef6 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/APIException.java @@ -23,6 +23,7 @@ import java.util.Collection; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import org.apache.guacamole.form.Field; +import org.apache.guacamole.protocol.GuacamoleStatus; /** * An exception that will result in the given error error information being @@ -60,6 +61,43 @@ public class APIException extends WebApplicationException { this(new APIError(type, message)); } + /** + * Creates a new APIException which represents an error that occurred within + * an intercepted Guacamole stream. The nature of that error will be + * described by a given status code, which should be the status code + * provided by the "ack" instruction that reported the error. + * + * @param status + * The Guacamole protocol status code describing the error that + * occurred within the intercepted stream. + * + * @param message + * An arbitrary human-readable message describing the error that + * occurred. + */ + public APIException(int status, String message) { + this(new APIError(status, message)); + } + + /** + * Creates a new APIException which represents an error that occurred within + * an intercepted Guacamole stream. The nature of that error will be + * described by a given Guacamole protocol status, which should be the + * status associated with the code provided by the "ack" instruction that + * reported the error. + * + * @param status + * The Guacamole protocol status describing the error that occurred + * within the intercepted stream. + * + * @param message + * An arbitrary human-readable message describing the error that + * occurred. + */ + public APIException(GuacamoleStatus status, String message) { + this(status.getGuacamoleStatusCode(), message); + } + /** * Creates a new APIException with the given type, message, and parameter * information. The corresponding APIError will be created from the diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java index 5adb13184..736907d4c 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionWrapper.java @@ -34,6 +34,7 @@ import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; import org.apache.guacamole.rest.auth.AuthenticationService; +import org.apache.guacamole.tunnel.GuacamoleStreamException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -248,6 +249,21 @@ public class RESTExceptionWrapper implements MethodInterceptor { } + // Errors from intercepted streams + catch (GuacamoleStreamException e) { + + // Generate default message + String message = e.getMessage(); + if (message == null) + message = "Error reported by stream."; + + throw new APIException( + e.getStatus(), + message + ); + + } + // All other errors catch (GuacamoleException e) { diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/GuacamoleStreamException.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/GuacamoleStreamException.java new file mode 100644 index 000000000..7bb4ef23e --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/GuacamoleStreamException.java @@ -0,0 +1,61 @@ +/* + * 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.tunnel; + +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.protocol.GuacamoleStatus; + +/** + * A generic exception thrown when an intercepted Guacamole stream has closed + * with an error condition. Guacamole streams report errors using the "ack" + * instruction, which provides a status code and human-readable message. + * + * @author Michael Jumper + */ +public class GuacamoleStreamException extends GuacamoleServerException { + + /** + * The error condition reported by the intercepted Guacamole stream. + */ + private final GuacamoleStatus status; + + /** + * Creates a new GuacamoleStreamException representing an error returned by + * an intercepted stream. + * + * @param status + * The status code of the error condition reported by the intercepted + * Guacamole stream. + * + * @param message + * The human readable description of the error that occurred, as + * as provided by the stream. + */ + public GuacamoleStreamException(GuacamoleStatus status, String message) { + super(message); + this.status = status; + } + + @Override + public GuacamoleStatus getStatus() { + return status; + } + +} diff --git a/guacamole/src/main/webapp/app/rest/types/Error.js b/guacamole/src/main/webapp/app/rest/types/Error.js index 85f2cf59e..43fdc9602 100644 --- a/guacamole/src/main/webapp/app/rest/types/Error.js +++ b/guacamole/src/main/webapp/app/rest/types/Error.js @@ -42,6 +42,14 @@ angular.module('rest').factory('Error', [function defineError() { */ this.message = template.message; + /** + * The Guacamole protocol status code associated with the error that + * occurred. This is only valid for errors of type STREAM_ERROR. + * + * @type Number + */ + this.statusCode = template.statusCode; + /** * The type string defining which values this parameter may contain, * as well as what properties are applicable. Valid types are listed @@ -110,7 +118,16 @@ angular.module('rest').factory('Error', [function defineError() { * * @type String */ - PERMISSION_DENIED : 'PERMISSION_DENIED' + PERMISSION_DENIED : 'PERMISSION_DENIED', + + /** + * An error occurred within an intercepted stream, terminating that + * stream. The Guacamole protocol status code of that error will be + * stored within statusCode. + * + * @type String + */ + STREAM_ERROR : 'STREAM_ERROR' };