GUACAMOLE-1298: Merge automatically limit HTTP request size.

This commit is contained in:
Virtually Nick
2021-02-25 20:19:00 -05:00
committed by GitHub
35 changed files with 488 additions and 259 deletions

View File

@@ -254,6 +254,11 @@
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@@ -435,33 +440,38 @@
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-assistedinject</artifactId>
<version>3.0</version>
<version>4.2.3</version>
</dependency>
<!-- Guice Servlet -->
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>3.0</version>
<version>4.2.3</version>
</dependency>
<!-- Jersey - JAX-RS Implementation -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.17.1</version>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.31</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.31</version>
</dependency>
<!-- Jersey - Guice extension -->
<!-- Guice bridge for HK2 - the dependency injection framework integrated with Jersey 2.x -->
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-guice</artifactId>
<version>1.17.1</version>
<groupId>org.glassfish.hk2</groupId>
<artifactId>guice-bridge</artifactId>
<version>2.6.1</version>
</dependency>
<!-- JSR-250 annotations -->
@@ -473,9 +483,9 @@
<!-- Jackson for JSON support -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17.1</version>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.31</version>
<!-- Exclude StAX API, which is part of Java 6 -->
<exclusions>

View File

@@ -0,0 +1,76 @@
/*
* 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;
import com.google.inject.Injector;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.ws.rs.ApplicationPath;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import org.slf4j.bridge.SLF4JBridgeHandler;
/**
* JAX-RS Application which serves as the root definition of the Guacamole
* REST API. The HK2 dependency injection used by Jersey is automatically
* bridged to Guice, allowing injections managed by Guice to be injected within
* classes served by Jersey.
*/
@ApplicationPath("/*")
public class GuacamoleApplication extends ResourceConfig {
/**
* Creates a new GuacamoleApplication which defines the Guacamole REST API,
* automatically configuring Jersey's HK2 dependency injection to
* additionally pull services from a Guice injector.
*
* @param servletContext
* The ServletContext which has already associated with a Guice
* injector via a GuacamoleServletContextListener.
*
* @param serviceLocator
* The HK2 service locator (injector).
*/
@Inject
public GuacamoleApplication(ServletContext servletContext,
ServiceLocator serviceLocator) {
// Bridge Jersey logging (java.util.logging) to SLF4J
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
// Bridge HK2 service locator with Guice injector
Injector guiceInjector = (Injector) servletContext.getAttribute(GuacamoleServletContextListener.GUICE_INJECTOR);
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge bridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
bridge.bridgeGuiceInjector(guiceInjector);
// Automatically scan for REST resources
packages("org.apache.guacamole.rest");
// Use Jackson for JSON
register(JacksonFeature.class);
}
}

View File

@@ -21,11 +21,12 @@ package org.apache.guacamole;
import org.apache.guacamole.tunnel.TunnelModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.google.inject.servlet.GuiceServletContextListener;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import javax.servlet.ServletContextEvent;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
@@ -41,9 +42,45 @@ import org.slf4j.LoggerFactory;
/**
* A ServletContextListener to listen for initialization of the servlet context
* in order to set up dependency injection.
*
* NOTE: Guacamole's REST API uses Jersey 2.x which does not natively support
* dependency injection using Guice. It DOES support dependency injection using
* HK2, which supports bi-directional bridging with Guice.
*
* The overall process is thus:
*
* 1. Application initialization proceeds using GuacamoleServletContextListener,
* a subclass of GuiceServletContextListener, with all HTTP requests being
* routed through GuiceFilter which serves as the absolute root.
*
* 2. GuacamoleServletContextListener prepares the Guice injector, storing the
* injector within the ServletContext such that it can later be bridged with
* HK2.
*
* 3. Several of the modules used to prepare the Guice injector are
* ServletModule subclasses, which define HTTP request paths that GuiceFilter
* should route to specific servlets. One of these paths is "/api/*" (the
* root of the REST API) which is routed to Jersey's ServletContainer servlet
* (the root of Jersey's JAX-RS implementation).
*
* 4. Configuration information passed to Jersey's ServletContainer tells Jersey
* to use the GuacamoleApplication class (a subclass of ResourceConfig) to
* define the rest of the resources and any other configuration.
*
* 5. When Jersey creates its instance of GuacamoleApplication, the
* initialization process of GuacamoleApplication pulls the Guice injector
* from the ServletContext, completes the HK2 bridging, and configures Jersey
* to automatically locate and inject all REST services.
*/
public class GuacamoleServletContextListener extends GuiceServletContextListener {
/**
* The name of the ServletContext attribute which will contain a reference
* to the Guice injector once the contextInitialized() event has been
* handled.
*/
public static final String GUICE_INJECTOR = "GUAC_GUICE_INJECTOR";
/**
* Logger for this class.
*/
@@ -65,6 +102,12 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
@Inject
private List<AuthenticationProvider> authProviders;
/**
* Internal reference to the Guice injector that was lazily created when
* getInjector() was first invoked.
*/
private final AtomicReference<Injector> guiceInjector = new AtomicReference<>();
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
@@ -78,33 +121,47 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
throw new RuntimeException(e);
}
// NOTE: The superclass implementation of contextInitialized() is
// expected to invoke getInjector(), hence the need to call AFTER
// setting up the environment and session map
super.contextInitialized(servletContextEvent);
// Inject any annotated members of this class
Injector injector = getInjector();
injector.injectMembers(this);
// Store reference to injector for use by Jersey and HK2 bridge
servletContextEvent.getServletContext().setAttribute(GUICE_INJECTOR, injector);
}
@Override
protected Injector getInjector() {
return guiceInjector.updateAndGet((current) -> {
// Create injector
Injector injector = Guice.createInjector(Stage.PRODUCTION,
new EnvironmentModule(environment),
new LogModule(environment),
new ExtensionModule(environment),
new RESTServiceModule(sessionMap),
new TunnelModule()
);
// Use existing injector if already created
if (current != null)
return current;
// Inject any annotated members of this class
injector.injectMembers(this);
// Create new injector if necessary
Injector injector = Guice.createInjector(Stage.PRODUCTION,
new EnvironmentModule(environment),
new LogModule(environment),
new ExtensionModule(environment),
new RESTServiceModule(sessionMap),
new TunnelModule()
);
return injector;
return injector;
});
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
super.contextDestroyed(servletContextEvent);
// Clean up reference to Guice injector
servletContextEvent.getServletContext().removeAttribute(GUICE_INJECTOR);
// Shutdown TokenSessionMap
if (sessionMap != null)
@@ -116,6 +173,9 @@ public class GuacamoleServletContextListener extends GuiceServletContextListener
authProvider.shutdown();
}
// Continue any Guice-specific cleanup
super.contextDestroyed(servletContextEvent);
}
}

View File

@@ -33,18 +33,15 @@ import org.apache.guacamole.GuacamoleException;
public class APIException extends WebApplicationException {
/**
* Construct a new APIException based on the given GuacamoleException and
* HTTP status. The details of the GuacamoleException relevant to the REST
* API will be exposed via an APIError.
*
* @param status
* The HTTP status which corresponds to the GuacamoleException.
* Construct a new APIException based on the given GuacamoleException. The
* details of the GuacamoleException relevant to the REST API will be
* exposed via an APIError.
*
* @param exception
* The GuacamoleException that occurred.
*/
public APIException(Response.Status status, GuacamoleException exception) {
super(Response.status(status)
public APIException(GuacamoleException exception) {
super(Response.status(exception.getHttpStatusCode())
.type(MediaType.APPLICATION_JSON)
.entity(new APIError(exception))
.build());

View File

@@ -0,0 +1,152 @@
/*
* 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.io.IOException;
import java.io.InputStream;
import org.apache.guacamole.GuacamoleClientOverrunException;
/**
* InputStream implementation which limits the body of REST API requests to
* a particular maximum size. If an attempt is made to read from a REST API
* request which exceeds this limit, the read attempt will be aborted by
* throwing an APIException.
*/
public class LimitedRequestInputStream extends InputStream {
/**
* The InputStream being limited.
*/
private final InputStream stream;
/**
* The maximum number of bytes to allow to be read or skipped.
*/
private final long maxLength;
/**
* The total number of bytes that have been read or skipped from the stream
* thus far.
*/
private long bytesRead = 0;
/**
* Wraps the given InputStream, ensuring that the overall number of bytes
* read or skipped does not exceed the given maximum length.
*
* @param stream
* The InputStream to limit.
*
* @param maxLength
* The maximum number of bytes to allow to be read or skipped.
*/
public LimitedRequestInputStream(InputStream stream, long maxLength) {
this.stream = stream;
this.maxLength = maxLength;
}
/**
* Immediately verifies that the stream length limit has not been exceeded.
* If the length limit has been exceeded, an APIException is thrown
* indicating that the request body is too large.
*
* @throws APIException
* If the length limit has been exceeded.
*/
private synchronized void recheckLength() throws APIException {
if (bytesRead > maxLength)
throw new APIException(new GuacamoleClientOverrunException("Request body/entity too large."));
}
/**
* Updates the current number of bytes read based on the return value of a
* read-like operation such as read() or skip(). If the maximum stream
* length is exceeded as a result of the read, an APIException indicating
* this is thrown.
*
* NOTE: To avoid unnecessary read operations, recheckLength() should be
* manually called before performing any read operation. This function will
* perform the same checks, but can inherently only do so AFTER the read
* operation has occurred.
*
* @param change
* The number of bytes that have been read or skipped, or -1 if the
* read-like operation has failed (and no bytes have been read).
*
* @return
* The provided number of bytes read/skipped.
*
* @throws APIException
* If the read-like operation that occurred has caused the stream
* length to exceed its maximum.
*/
private synchronized long limitedRead(long change) throws APIException {
if (change != -1) {
bytesRead += change;
recheckLength();
}
return change;
}
@Override
public void close() throws IOException {
stream.close();
}
@Override
public int available() throws IOException {
return stream.available();
}
@Override
public long skip(long l) throws IOException {
recheckLength();
return limitedRead(stream.skip(l));
}
@Override
public int read(byte[] bytes, int i, int i1) throws IOException {
recheckLength();
return (int) limitedRead(stream.read(bytes, i, i1));
}
@Override
public int read(byte[] bytes) throws IOException {
recheckLength();
return (int) limitedRead(stream.read(bytes));
}
@Override
public int read() throws IOException {
recheckLength();
int value = stream.read();
if (value != -1)
limitedRead(1);
return value;
}
}

View File

@@ -1,35 +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 java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.HttpMethod;
/**
* An annotation for using the HTTP PATCH method in the REST endpoints.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH {}

View File

@@ -19,9 +19,10 @@
package org.apache.guacamole.rest;
import com.google.inject.Inject;
import com.google.inject.Singleton;
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;
import javax.ws.rs.core.Response;
@@ -83,6 +84,10 @@ public class RESTExceptionMapper implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable t) {
// Pass WebApplicationException responses through untouched
if (t instanceof WebApplicationException)
return ((WebApplicationException) t).getResponse();
// Ensure any associated session is invalidated if unauthorized
if (t instanceof GuacamoleUnauthorizedException) {
String token = getAuthenticationToken();

View File

@@ -1,128 +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.matcher.AbstractMatcher;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import org.apache.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 <code>@GET</code> or <code>@POST</code>. Any method which
* throws GuacamoleException and is annotated with an annotation that is
* annotated with <code>@HttpMethod</code> will match.
*/
public class RESTMethodMatcher extends AbstractMatcher<Method> {
/**
* 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
* <code>@HttpMethod</code> or <code>@Path</code>.
*
* @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;
// A method is a REST method if it is annotated with @Path
if (Path.class.isAssignableFrom(annotationType))
return true;
}
// A method is also REST method if it overrides a REST method within
// the superclass
Class<?> superclass = method.getDeclaringClass().getSuperclass();
if (superclass != null) {
// Recheck against identical method within superclass
try {
return isRESTMethod(superclass.getMethod(method.getName(),
method.getParameterTypes()));
}
// If there is no such method, then this method cannot possibly be
// a REST method
catch (NoSuchMethodException e) {
return false;
}
}
// Lacking a superclass, the search stops here - it's not a REST 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);
}
}

View File

@@ -19,18 +19,14 @@
package org.apache.guacamole.rest;
import org.apache.guacamole.rest.event.ListenerService;
import org.apache.guacamole.rest.session.UserContextResourceFactory;
import org.apache.guacamole.rest.session.SessionRESTService;
import com.google.inject.Scopes;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.matcher.Matchers;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import org.aopalliance.intercept.MethodInterceptor;
import java.util.Collections;
import org.apache.guacamole.rest.event.ListenerService;
import org.apache.guacamole.rest.session.UserContextResourceFactory;
import org.apache.guacamole.GuacamoleApplication;
import org.apache.guacamole.rest.activeconnection.ActiveConnectionModule;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
import org.apache.guacamole.rest.auth.TokenRESTService;
import org.apache.guacamole.rest.auth.AuthTokenGenerator;
import org.apache.guacamole.rest.auth.AuthenticationService;
import org.apache.guacamole.rest.auth.DecorationService;
@@ -38,15 +34,14 @@ import org.apache.guacamole.rest.auth.SecureRandomAuthTokenGenerator;
import org.apache.guacamole.rest.auth.TokenSessionMap;
import org.apache.guacamole.rest.connection.ConnectionModule;
import org.apache.guacamole.rest.connectiongroup.ConnectionGroupModule;
import org.apache.guacamole.rest.extension.ExtensionRESTService;
import org.apache.guacamole.rest.language.LanguageRESTService;
import org.apache.guacamole.rest.patch.PatchRESTService;
import org.apache.guacamole.rest.session.SessionResourceFactory;
import org.apache.guacamole.rest.sharingprofile.SharingProfileModule;
import org.apache.guacamole.rest.tunnel.TunnelCollectionResourceFactory;
import org.apache.guacamole.rest.tunnel.TunnelResourceFactory;
import org.apache.guacamole.rest.user.UserModule;
import org.apache.guacamole.rest.usergroup.UserGroupModule;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.servlet.ServletProperties;
import org.webjars.servlet.WebjarsServlet;
/**
@@ -84,17 +79,7 @@ public class RESTServiceModule extends ServletModule {
bind(AuthTokenGenerator.class).to(SecureRandomAuthTokenGenerator.class);
bind(DecorationService.class);
// Automatically translate GuacamoleExceptions for REST methods
bind(RESTExceptionMapper.class);
// Set up the API endpoints
bind(ExtensionRESTService.class);
bind(LanguageRESTService.class);
bind(PatchRESTService.class);
bind(TokenRESTService.class);
// Root-level resources
bind(SessionRESTService.class);
install(new FactoryModuleBuilder().build(SessionResourceFactory.class));
install(new FactoryModuleBuilder().build(TunnelCollectionResourceFactory.class));
install(new FactoryModuleBuilder().build(TunnelResourceFactory.class));
@@ -108,10 +93,12 @@ public class RESTServiceModule extends ServletModule {
install(new UserModule());
install(new UserGroupModule());
// Set up the servlet and JSON mappings
bind(GuiceContainer.class);
bind(JacksonJsonProvider.class).in(Scopes.SINGLETON);
serve("/api/*").with(GuiceContainer.class);
// Serve REST services using Jersey 2.x
bind(ServletContainer.class).in(Scopes.SINGLETON);
serve("/api/*").with(ServletContainer.class, Collections.singletonMap(
ServletProperties.JAXRS_APPLICATION_CLASS,
GuacamoleApplication.class.getName()
));
// Serve Webjar JavaScript dependencies
bind(WebjarsServlet.class).in(Scopes.SINGLETON);

View File

@@ -0,0 +1,108 @@
/*
* 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.io.IOException;
import java.io.InputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.LongGuacamoleProperty;
/**
* Filter which restricts REST API requests to a particular maximum size.
*/
@Singleton
@Provider
public class RequestSizeFilter implements ContainerRequestFilter {
/**
* Informs the RequestSizeFilter to NOT enforce its request size limits on
* requests serviced by the annotated method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface DoNotLimit {}
/**
* The default maximum number of bytes to accept within the entity body of
* any particular REST request.
*/
private final long DEFAULT_MAX_REQUEST_SIZE = 2097152;
/**
* The maximum number of bytes to accept within the entity body of any
* particular REST request. If not specified, requests will be limited to
* 2 MB by default. Specifying 0 disables request size limitations.
*/
private final LongGuacamoleProperty API_MAX_REQUEST_SIZE = new LongGuacamoleProperty() {
@Override
public String getName() { return "api-max-request-size"; }
};
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* Information describing the resource that was requested.
*/
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext context) throws IOException {
// Retrieve configured request size limits
final long maxRequestSize;
try {
maxRequestSize = environment.getProperty(API_MAX_REQUEST_SIZE, DEFAULT_MAX_REQUEST_SIZE);
}
catch (GuacamoleException e) {
throw new APIException(e);
}
// Ignore request size if limit is disabled
if (maxRequestSize == 0 || resourceInfo.getResourceMethod().isAnnotationPresent(DoNotLimit.class))
return;
// Restrict maximum size of requests which have an input stream
// available to be limited
InputStream stream = context.getEntityStream();
if (stream != null)
context.setEntityStream(new LimitedRequestInputStream(stream, maxRequestSize));
}
}

View File

@@ -19,9 +19,9 @@
package org.apache.guacamole.rest.activeconnection;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

View File

@@ -19,10 +19,10 @@
package org.apache.guacamole.rest.auth;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;

View File

@@ -19,9 +19,9 @@
package org.apache.guacamole.rest.auth;
import com.google.inject.Inject;
import java.util.Iterator;
import java.util.List;
import javax.inject.Inject;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AuthenticatedUser;

View File

@@ -20,10 +20,10 @@
package org.apache.guacamole.rest.auth;
import com.google.common.io.BaseEncoding;
import com.google.inject.Inject;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;

View File

@@ -19,10 +19,10 @@
package org.apache.guacamole.rest.connection;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

View File

@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -44,7 +45,6 @@ import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
import org.apache.guacamole.rest.APIPatch;
import org.apache.guacamole.rest.PATCH;
/**
* A REST resource which abstracts the operations available on all Guacamole

View File

@@ -20,7 +20,7 @@
package org.apache.guacamole.rest.event;
import java.util.List;
import com.google.inject.Inject;
import javax.inject.Inject;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.event.listener.Listener;

View File

@@ -19,8 +19,8 @@
package org.apache.guacamole.rest.extension;
import com.google.inject.Inject;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import org.apache.guacamole.GuacamoleException;

View File

@@ -109,10 +109,7 @@ public class APISortPredicate {
// Bail out if sort property is not valid
catch (IllegalArgumentException e) {
throw new APIException(
Response.Status.BAD_REQUEST,
new GuacamoleClientException(String.format("Invalid sort property: \"%s\"", value))
);
throw new APIException(new GuacamoleClientException(String.format("Invalid sort property: \"%s\"", value)));
}
}

View File

@@ -23,13 +23,13 @@ import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.RelatedObjectSet;
import org.apache.guacamole.rest.APIPatch;
import org.apache.guacamole.rest.PATCH;
/**
* A REST resource which abstracts the operations available on arbitrary sets

View File

@@ -19,8 +19,8 @@
package org.apache.guacamole.rest.language;
import com.google.inject.Inject;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

View File

@@ -19,12 +19,12 @@
package org.apache.guacamole.rest.patch;
import com.google.inject.Inject;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

View File

@@ -22,6 +22,7 @@ package org.apache.guacamole.rest.permission;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.apache.guacamole.GuacamoleClientException;
@@ -31,7 +32,6 @@ import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.Permission;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.rest.APIPatch;
import org.apache.guacamole.rest.PATCH;
/**
* A REST resource which abstracts the operations available on the permissions

View File

@@ -19,7 +19,7 @@
package org.apache.guacamole.rest.session;
import com.google.inject.Inject;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

View File

@@ -19,9 +19,9 @@
package org.apache.guacamole.rest.session;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

View File

@@ -21,9 +21,9 @@ package org.apache.guacamole.rest.session;
import org.apache.guacamole.rest.directory.DirectoryResource;
import org.apache.guacamole.rest.directory.DirectoryResourceFactory;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

View File

@@ -31,6 +31,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.StreamingOutput;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.rest.RequestSizeFilter;
import org.apache.guacamole.tunnel.StreamInterceptingTunnel;
/**
@@ -127,6 +128,7 @@ public class StreamResource {
*/
@POST
@Consumes(MediaType.WILDCARD)
@RequestSizeFilter.DoNotLimit
public void setStreamContents(InputStream data) throws GuacamoleException {
// Send input over stream

View File

@@ -19,11 +19,11 @@
package org.apache.guacamole.rest.tunnel;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

View File

@@ -19,9 +19,9 @@
package org.apache.guacamole.rest.tunnel;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;

View File

@@ -19,10 +19,10 @@
package org.apache.guacamole.tunnel;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleSession;

View File

@@ -19,11 +19,10 @@
package org.apache.guacamole.tunnel.http;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.tunnel.TunnelRequestService;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;

View File

@@ -19,8 +19,8 @@
package org.apache.guacamole.tunnel.websocket.jetty8;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.tunnel.TunnelRequestService;

View File

@@ -19,8 +19,8 @@
package org.apache.guacamole.tunnel.websocket.jetty9;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.apache.guacamole.tunnel.TunnelRequestService;

View File

@@ -19,8 +19,8 @@
package org.apache.guacamole.tunnel.websocket.tomcat;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.tunnel.TunnelRequestService;

View File

@@ -28,7 +28,7 @@
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- Guice -->
<!-- Route all requests through Guice -->
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
@@ -37,7 +37,6 @@
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.apache.guacamole.GuacamoleServletContextListener</listener-class>
</listener>