diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java index 85430ae19..2aae289f8 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/RESTExceptionMapper.java @@ -21,7 +21,6 @@ package org.apache.guacamole.rest; import javax.inject.Inject; import javax.inject.Singleton; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -32,6 +31,7 @@ import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnauthorizedException; import org.apache.guacamole.rest.auth.AuthenticationService; +import org.glassfish.jersey.server.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,37 +50,20 @@ public class RESTExceptionMapper implements ExceptionMapper { private final Logger logger = LoggerFactory.getLogger(RESTExceptionMapper.class); /** - * The HttpServletRequest for the Throwable being intercepted. Despite this - * class being a Singleton, this object will always be scoped with the - * current request for the Throwable that is being processed by this class. + * The ContainerRequest for HTTP request that resulted in the Throwable + * being intercepted. Despite this class being a Singleton, this object will + * always be scoped with the current request for the Throwable that is being + * processed by this class. */ @Context - private HttpServletRequest request; + private ContainerRequest 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() { - String token = request.getParameter("token"); - if (token != null && !token.isEmpty()) - return token; - - return null; - - } - @Override public Response toResponse(Throwable t) { @@ -90,8 +73,7 @@ public class RESTExceptionMapper implements ExceptionMapper { // Ensure any associated session is invalidated if unauthorized if (t instanceof GuacamoleUnauthorizedException) { - String token = getAuthenticationToken(); - + String token = authenticationService.getAuthenticationToken(request); if (authenticationService.destroyGuacamoleSession(token)) logger.debug("Implicitly invalidated session for token \"{}\"", token); } @@ -135,4 +117,4 @@ public class RESTExceptionMapper implements ExceptionMapper { } -} \ No newline at end of file +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/TokenParam.java b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParam.java new file mode 100644 index 000000000..53c972254 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParam.java @@ -0,0 +1,34 @@ +/* + * 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation which automatically maps the authentication token used by + * Guacamole's REST API, regardless of whether that token is received via an + * HTTP header or via a query parameter. + */ +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TokenParam {} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/TokenParamProvider.java b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParamProvider.java new file mode 100644 index 000000000..b0da96188 --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/rest/TokenParamProvider.java @@ -0,0 +1,60 @@ +/* + * 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 java.util.function.Function; +import javax.inject.Inject; +import javax.ws.rs.ext.Provider; +import org.apache.guacamole.rest.auth.AuthenticationService; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.model.Parameter; +import org.glassfish.jersey.server.spi.internal.ValueParamProvider; + +/** + * Provider which automatically maps Guacamole authentication tokens received + * via REST API requests to parameters that have been annotated with the + * @TokenParam annotation. + */ +@Provider +public class TokenParamProvider implements ValueParamProvider { + + /** + * Service for authenticating users and working with the resulting + * authentication tokens. + */ + @Inject + private AuthenticationService authenticationService; + + @Override + public Function getValueProvider(Parameter parameter) { + + if (parameter.getAnnotation(TokenParam.class) == null) + return null; + + return (request) -> authenticationService.getAuthenticationToken(request); + + } + + @Override + public PriorityType getPriority() { + return Priority.HIGH; + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java index a663ab783..ce8a9fb0c 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/auth/AuthenticationService.java @@ -41,6 +41,7 @@ import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsExce import org.apache.guacamole.net.event.AuthenticationFailureEvent; import org.apache.guacamole.net.event.AuthenticationSuccessEvent; import org.apache.guacamole.rest.event.ListenerService; +import org.glassfish.jersey.server.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,6 +92,18 @@ public class AuthenticationService { @Inject private ListenerService listenerService; + /** + * The name of the HTTP header that may contain the authentication token + * used by the Guacamole REST API. + */ + public static final String TOKEN_HEADER_NAME = "Guacamole-Token"; + + /** + * The name of the query parameter that may contain the authentication + * token used by the Guacamole REST API. + */ + public static final String TOKEN_PARAMETER_NAME = "token"; + /** * Regular expression which matches any IPv4 address. */ @@ -541,4 +554,34 @@ public class AuthenticationService { return getGuacamoleSession(authToken).getUserContexts(); } + /** + * Returns the authentication token sent within the given request, if + * present, or null if otherwise. Authentication tokens may be sent via + * the "Guacamole-Token" header or the "token" query parameter. If both + * the header and a parameter are used, the header is given priority. + * + * @param request + * The HTTP request to retrieve the authentication token from. + * + * @return + * The authentication token within the given request, or null if no + * token is present. + */ + public String getAuthenticationToken(ContainerRequest request) { + + // Give priority to token within HTTP header + String token = request.getHeaderString(TOKEN_HEADER_NAME); + if (token != null && !token.isEmpty()) + return token; + + // If no token was provided via HTTP headers, fall back to using + // query parameters + token = request.getUriInfo().getQueryParameters().getFirst(TOKEN_PARAMETER_NAME); + if (token != null && !token.isEmpty()) + return token; + + return null; + + } + } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java index 546026b53..31891b3d4 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/session/SessionRESTService.java @@ -23,10 +23,10 @@ import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSession; +import org.apache.guacamole.rest.TokenParam; import org.apache.guacamole.rest.auth.AuthenticationService; /** @@ -67,7 +67,7 @@ public class SessionRESTService { * If the authentication token is invalid. */ @Path("/") - public SessionResource getSessionResource(@QueryParam("token") String authToken) + public SessionResource getSessionResource(@TokenParam String authToken) throws GuacamoleException { // Return a resource exposing the retrieved session