diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExposure.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExposure.java
deleted file mode 100644
index 2a450c2c3..000000000
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExposure.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2014 Glyptodon LLC
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-package org.glyptodon.guacamole.net.basic.rest;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Marks that a method exposes functionality from the Guacamole AuthenticationProvider
- * using a REST interface.
- *
- * @author James Muehlner
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD})
-public @interface AuthProviderRESTExposure {}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java
similarity index 90%
rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java
rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java
index d73903ef5..9cb0bc807 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/AuthProviderRESTExceptionWrapper.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTExceptionWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Glyptodon LLC
+ * Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -34,13 +34,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * A method interceptor to wrap some custom exception handling around methods
- * that expose AuthenticationProvider functionality through the REST interface.
- * Translates various types of GuacamoleExceptions into appropriate HTTP responses.
- *
+ * 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.
+ *
* @author James Muehlner
+ * @author Michael Jumper
*/
-public class AuthProviderRESTExceptionWrapper implements MethodInterceptor {
+public class RESTExceptionWrapper implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTMethodMatcher.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTMethodMatcher.java
new file mode 100644
index 000000000..643addcd9
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTMethodMatcher.java
@@ -0,0 +1,109 @@
+/*
+ * 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.matcher.AbstractMatcher;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import javax.ws.rs.HttpMethod;
+import org.glyptodon.guacamole.GuacamoleException;
+
+/**
+ * A Guice Matcher which matches only methods which throw GuacamoleException
+ * (or a subclass thereof) and are explicitly annotated as with an HTTP method
+ * annotation like @GET
or @POST
. Any method which
+ * throws GuacamoleException and is annotated with an annotation that is
+ * annotated with @HttpMethod
will match.
+ *
+ * @author Michael Jumper
+ */
+public class RESTMethodMatcher extends AbstractMatcher {
+
+ /**
+ * Returns whether the given method throws the specified exception type,
+ * including any subclasses of that type.
+ *
+ * @param method
+ * The method to test.
+ *
+ * @param exceptionType
+ * The exception type to test for.
+ *
+ * @return
+ * true if the given method throws an exception of the specified type,
+ * false otherwise.
+ */
+ private boolean methodThrowsException(Method method,
+ Class extends Exception> exceptionType) {
+
+ // Check whether the method throws an exception of the specified type
+ for (Class> thrownType : method.getExceptionTypes()) {
+ if (exceptionType.isAssignableFrom(thrownType))
+ return true;
+ }
+
+ // No such exception is declared to be thrown
+ return false;
+
+ }
+
+ /**
+ * Returns whether the given method is annotated as a REST method. A REST
+ * method is annotated with an annotation which is annotated with
+ * @HttpMethod
.
+ *
+ * @param method
+ * The method to test.
+ *
+ * @return
+ * true if the given method is annotated as a REST method, false
+ * otherwise.
+ */
+ private boolean isRESTMethod(Method method) {
+
+ // Check whether the required REST annotations are present
+ for (Annotation annotation : method.getAnnotations()) {
+
+ // A method is a REST method if it is annotated with @HttpMethod
+ Class extends Annotation> annotationType = annotation.annotationType();
+ if (annotationType.isAnnotationPresent(HttpMethod.class))
+ return true;
+
+ }
+
+ // The method is not an HTTP method
+ return false;
+
+ }
+
+ @Override
+ public boolean matches(Method method) {
+
+ // Guacamole REST methods are REST methods which throw
+ // GuacamoleExceptions
+ return isRESTMethod(method)
+ && methodThrowsException(method, GuacamoleException.class);
+
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java
index 478c2182e..48db9857b 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/RESTServletModule.java
@@ -45,11 +45,11 @@ public class RESTServletModule extends ServletModule {
@Override
protected void configureServlets() {
- // Bind @AuthProviderRESTExposure annotation
+ // Automatically translate GuacamoleExceptions for REST methods
bindInterceptor(
Matchers.any(),
- Matchers.annotatedWith(AuthProviderRESTExposure.class),
- new AuthProviderRESTExceptionWrapper()
+ new RESTMethodMatcher(),
+ new RESTExceptionWrapper()
);
// Bind convenience services used by the REST API
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/activeconnection/ActiveConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/activeconnection/ActiveConnectionRESTService.java
index 42ae1c15f..2f2b69994 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/activeconnection/ActiveConnectionRESTService.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/activeconnection/ActiveConnectionRESTService.java
@@ -47,7 +47,6 @@ import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
import org.glyptodon.guacamole.net.basic.rest.APIPatch;
-import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
import org.glyptodon.guacamole.net.basic.rest.PATCH;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
@@ -107,7 +106,6 @@ public class ActiveConnectionRESTService {
* If an error is encountered while retrieving active connections.
*/
@GET
- @AuthProviderRESTExposure
public Map getActiveConnections(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@QueryParam("permission") List permissions)
@@ -166,7 +164,6 @@ public class ActiveConnectionRESTService {
* If an error occurs while deleting the active connections.
*/
@PATCH
- @AuthProviderRESTExposure
public void patchTunnels(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
List> patches) throws GuacamoleException {
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java
index 998cd5e44..4351b890e 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/auth/TokenRESTService.java
@@ -39,6 +39,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.xml.bind.DatatypeConverter;
import org.glyptodon.guacamole.GuacamoleException;
+import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
@@ -49,10 +50,7 @@ import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo;
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleCredentialsException;
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
-import org.glyptodon.guacamole.net.basic.rest.APIError;
import org.glyptodon.guacamole.net.basic.rest.APIRequest;
-import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
-import org.glyptodon.guacamole.net.basic.rest.APIException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -464,7 +462,6 @@ public class TokenRESTService {
* If an error prevents successful authentication.
*/
@POST
- @AuthProviderRESTExposure
public APIAuthenticationResult createToken(@FormParam("username") String username,
@FormParam("password") String password,
@FormParam("token") String token,
@@ -523,16 +520,20 @@ public class TokenRESTService {
* Invalidates a specific auth token, effectively logging out the associated
* user.
*
- * @param authToken The token being invalidated.
+ * @param authToken
+ * The token being invalidated.
+ *
+ * @throws GuacamoleException
+ * If the specified token does not exist.
*/
@DELETE
@Path("/{token}")
- @AuthProviderRESTExposure
- public void invalidateToken(@PathParam("token") String authToken) {
+ public void invalidateToken(@PathParam("token") String authToken)
+ throws GuacamoleException {
GuacamoleSession session = tokenSessionMap.remove(authToken);
if (session == null)
- throw new APIException(APIError.Type.NOT_FOUND, "No such token.");
+ throw new GuacamoleResourceNotFoundException("No such token.");
session.invalidate();
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java
index 270e3c302..caa4aa99a 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connection/ConnectionRESTService.java
@@ -49,7 +49,6 @@ import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
import org.glyptodon.guacamole.net.auth.permission.SystemPermission;
import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
-import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
@@ -105,7 +104,6 @@ public class ConnectionRESTService {
*/
@GET
@Path("/{connectionID}")
- @AuthProviderRESTExposure
public APIConnection getConnection(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionID") String connectionID)
@@ -141,7 +139,6 @@ public class ConnectionRESTService {
*/
@GET
@Path("/{connectionID}/parameters")
- @AuthProviderRESTExposure
public Map getConnectionParameters(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionID") String connectionID)
@@ -195,7 +192,6 @@ public class ConnectionRESTService {
*/
@GET
@Path("/{connectionID}/history")
- @AuthProviderRESTExposure
public List getConnectionHistory(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionID") String connectionID)
@@ -235,7 +231,6 @@ public class ConnectionRESTService {
*/
@DELETE
@Path("/{connectionID}")
- @AuthProviderRESTExposure
public void deleteConnection(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionID") String connectionID)
@@ -275,7 +270,6 @@ public class ConnectionRESTService {
*/
@POST
@Produces(MediaType.TEXT_PLAIN)
- @AuthProviderRESTExposure
public String createConnection(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
APIConnection connection) throws GuacamoleException {
@@ -320,7 +314,6 @@ public class ConnectionRESTService {
*/
@PUT
@Path("/{connectionID}")
- @AuthProviderRESTExposure
public void updateConnection(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionID") String connectionID,
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java
index 5899d32d7..77bcbdfae 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/connectiongroup/ConnectionGroupRESTService.java
@@ -41,7 +41,6 @@ import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
-import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.slf4j.Logger;
@@ -96,7 +95,6 @@ public class ConnectionGroupRESTService {
*/
@GET
@Path("/{connectionGroupID}")
- @AuthProviderRESTExposure
public APIConnectionGroup getConnectionGroup(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionGroupID") String connectionGroupID)
@@ -138,7 +136,6 @@ public class ConnectionGroupRESTService {
*/
@GET
@Path("/{connectionGroupID}/tree")
- @AuthProviderRESTExposure
public APIConnectionGroup getConnectionGroupTree(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionGroupID") String connectionGroupID,
@@ -176,7 +173,6 @@ public class ConnectionGroupRESTService {
*/
@DELETE
@Path("/{connectionGroupID}")
- @AuthProviderRESTExposure
public void deleteConnectionGroup(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionGroupID") String connectionGroupID)
@@ -218,7 +214,6 @@ public class ConnectionGroupRESTService {
*/
@POST
@Produces(MediaType.TEXT_PLAIN)
- @AuthProviderRESTExposure
public String createConnectionGroup(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
APIConnectionGroup connectionGroup) throws GuacamoleException {
@@ -263,7 +258,6 @@ public class ConnectionGroupRESTService {
*/
@PUT
@Path("/{connectionGroupID}")
- @AuthProviderRESTExposure
public void updateConnectionGroup(@QueryParam("token") String authToken,
@PathParam("dataSource") String authProviderIdentifier,
@PathParam("connectionGroupID") String connectionGroupID,
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/schema/SchemaRESTService.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/schema/SchemaRESTService.java
index 6a09cba32..66a2aa78f 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/schema/SchemaRESTService.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/rest/schema/SchemaRESTService.java
@@ -38,7 +38,6 @@ import org.glyptodon.guacamole.environment.LocalEnvironment;
import org.glyptodon.guacamole.form.Form;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.GuacamoleSession;
-import org.glyptodon.guacamole.net.basic.rest.AuthProviderRESTExposure;
import org.glyptodon.guacamole.net.basic.rest.ObjectRetrievalService;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.glyptodon.guacamole.protocols.ProtocolInfo;
@@ -86,7 +85,6 @@ public class SchemaRESTService {
*/
@GET
@Path("/users/attributes")
- @AuthProviderRESTExposure
public Collection