From f89fe9f2a3efef3cdf46198877fd134b73c00163 Mon Sep 17 00:00:00 2001 From: Josh Eckels Date: Mon, 24 Jun 2024 16:48:53 -0700 Subject: [PATCH] Support CORS filter and more Tomcat connector properties (#839) --- .../src/org/labkey/embedded/LabKeyServer.java | 166 ++++++++++++++++-- .../LabKeyTomcatServletWebServerFactory.java | 53 +++++- 2 files changed, 199 insertions(+), 20 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index bb7d660df6..4450e358dc 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -29,6 +29,7 @@ public class LabKeyServer public static final String SERVER_GUID_PARAMETER_NAME = "org.labkey.mothership." + SERVER_GUID; public static final String SERVER_SSL_KEYSTORE = "org.labkey.serverSslKeystore"; public static final String CUSTOM_LOG4J_CONFIG = "org.labkey.customLog4JConfig"; + public static final String CORS_PREFIX = "cors."; static final String MAX_TOTAL_CONNECTIONS_DEFAULT = "50"; static final String MAX_IDLE_DEFAULT = "10"; static final String MAX_WAIT_MILLIS_DEFAULT = "120000"; @@ -183,9 +184,9 @@ public void setConditionUnless(String conditionUnless) } @Bean - public ManagementProperties managementSource() + public ManagementServerProperties managementServerSource() { - return new ManagementProperties(); + return new ManagementServerProperties(); } @Bean @@ -194,40 +195,175 @@ public LoggingProperties loggingSource() return new LoggingProperties(); } + @Bean + public TomcatProperties tomcatProperties() + { + return new TomcatProperties(); + } /** * This lets us snoop on the Spring Boot config for deploying the management endpoint on a different port, as * we don't want to deploy LK on that port */ @Configuration - @ConfigurationProperties("management") - public static class ManagementProperties + @ConfigurationProperties("management.server") + public static class ManagementServerProperties { - private ServerProperties _server; + private int _port; - public ServerProperties getServer() + public int getPort() { - return _server; + return _port; } - public void setServer(ServerProperties server) + public void setPort(int port) { - _server = server; + _port = port; } } - public static class ServerProperties + /** Values that we'll propagate to org.apache.catalina.filters.CorsFilter */ + public static class CorsProperties { - private int _port; + private String _allowedOrigins; + private String _allowedMethods; + private String _allowedHeaders; + private String _exposedHeaders; + private String _supportCredentials; + private String _urlPattern; + private String _preflightMaxAge; + private String _requestDecorate; - public int getPort() + public String getAllowedOrigins() { - return _port; + return _allowedOrigins; } - public void setPort(int port) + public void setAllowedOrigins(String allowedOrigins) { - _port = port; + _allowedOrigins = allowedOrigins; + } + + public String getAllowedMethods() + { + return _allowedMethods; + } + + public void setAllowedMethods(String allowedMethods) + { + _allowedMethods = allowedMethods; + } + + public String getAllowedHeaders() + { + return _allowedHeaders; + } + + public void setAllowedHeaders(String allowedHeaders) + { + _allowedHeaders = allowedHeaders; + } + + public String getExposedHeaders() + { + return _exposedHeaders; + } + + public void setExposedHeaders(String exposedHeaders) + { + _exposedHeaders = exposedHeaders; + } + + public String getSupportCredentials() + { + return _supportCredentials; + } + + public void setSupportCredentials(String supportCredentials) + { + _supportCredentials = supportCredentials; + } + + public String getUrlPattern() + { + return _urlPattern; + } + + public void setUrlPattern(String urlPattern) + { + _urlPattern = urlPattern; + } + + public String getPreflightMaxAge() + { + return _preflightMaxAge; + } + + public void setPreflightMaxAge(String preflightMaxAge) + { + _preflightMaxAge = preflightMaxAge; + } + + public String getRequestDecorate() + { + return _requestDecorate; + } + + public void setRequestDecorate(String requestDecorate) + { + _requestDecorate = requestDecorate; + } + } + + /** Add some properties that Spring Boot doesn't support setting. See issue 50690 */ + @Configuration + @ConfigurationProperties("server.tomcat") + public static class TomcatProperties + { + private Boolean _useSendfile; + private Boolean _disableUploadTimeout; + private Boolean _useBodyEncodingForURI; + + private CorsProperties _cors; + + public Boolean getUseSendfile() + { + return _useSendfile; + } + + public void setUseSendfile(Boolean useSendfile) + { + _useSendfile = useSendfile; + } + + public Boolean getDisableUploadTimeout() + { + return _disableUploadTimeout; + } + + public void setDisableUploadTimeout(Boolean disableUploadTimeout) + { + _disableUploadTimeout = disableUploadTimeout; + } + + public Boolean getUseBodyEncodingForURI() + { + return _useBodyEncodingForURI; + } + + public void setUseBodyEncodingForURI(Boolean useBodyEncodingForURI) + { + _useBodyEncodingForURI = useBodyEncodingForURI; + } + + public CorsProperties getCors() + { + return _cors; + } + + public void setCors(CorsProperties cors) + { + _cors = cors; } } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 51afaab4de..d905c812f5 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -7,6 +7,7 @@ import org.apache.catalina.valves.JsonAccessLogValve; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.coyote.http11.AbstractHttp11Protocol; import org.apache.tomcat.util.descriptor.web.ContextResource; import org.labkey.bootstrap.ConfigException; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; @@ -19,6 +20,7 @@ import java.util.Map; import java.util.Objects; +import static org.labkey.embedded.LabKeyServer.CORS_PREFIX; import static org.labkey.embedded.LabKeyServer.CUSTOM_LOG4J_CONFIG; import static org.labkey.embedded.LabKeyServer.SERVER_GUID_PARAMETER_NAME; import static org.labkey.embedded.LabKeyServer.SERVER_SSL_KEYSTORE; @@ -31,6 +33,27 @@ class LabKeyTomcatServletWebServerFactory extends TomcatServletWebServerFactory public LabKeyTomcatServletWebServerFactory(LabKeyServer server) { _server = server; + + addConnectorCustomizers(connector -> { + LabKeyServer.TomcatProperties props = _server.tomcatProperties(); + + if (props.getUseBodyEncodingForURI() != null) + { + connector.setUseBodyEncodingForURI(props.getUseBodyEncodingForURI()); + } + + if (connector.getProtocolHandler() instanceof AbstractHttp11Protocol handler) + { + if (props.getDisableUploadTimeout() != null) + { + handler.setDisableUploadTimeout(props.getDisableUploadTimeout()); + } + if (props.getUseSendfile() != null) + { + handler.setUseSendfile(props.getUseSendfile()); + } + } + }); } @Override @@ -48,10 +71,10 @@ protected void prepareContext(Host host, ServletContextInitializer[] initializer @Override protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { - LabKeyServer.ManagementProperties props = _server.managementSource(); + LabKeyServer.ManagementServerProperties props = _server.managementServerSource(); // Don't deploy LK webapp on the separate instance running on the management port - if (props == null || props.getServer() == null || props.getServer().getPort() != getPort()) + if (props == null || props.getPort() != getPort()) { tomcat.enableNaming(); @@ -164,10 +187,22 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) } context.addParameter(CUSTOM_LOG4J_CONFIG, Boolean.toString(customLog4J)); - // Add serverGUID for mothership - it tells mothership that 2 instances of a server should be considered the same for metrics gathering purposes. - if (null != contextProperties.getServerGUID()) + // Add serverGUID for mothership - it tells mothership that 2 instances of a server should be considered the same for metrics gathering purposes + addContextProperty(context, contextProperties.getServerGUID(), SERVER_GUID_PARAMETER_NAME); + + LabKeyServer.TomcatProperties tomcatProperties = _server.tomcatProperties(); + if (tomcatProperties != null && tomcatProperties.getCors() != null) { - context.addParameter(SERVER_GUID_PARAMETER_NAME, contextProperties.getServerGUID()); + // Push these into the context so that ApiModule can register Tomcat's CorsFilter as desired + LabKeyServer.CorsProperties corsProperties = tomcatProperties.getCors(); + addContextProperty(context, corsProperties.getAllowedOrigins(), CORS_PREFIX + "allowedOrigins"); + addContextProperty(context, corsProperties.getAllowedMethods(), CORS_PREFIX + "allowedMethods"); + addContextProperty(context, corsProperties.getAllowedHeaders(), CORS_PREFIX + "allowedHeaders"); + addContextProperty(context, corsProperties.getExposedHeaders(), CORS_PREFIX + "exposedHeaders"); + addContextProperty(context, corsProperties.getSupportCredentials(), CORS_PREFIX + "supportCredentials"); + addContextProperty(context, corsProperties.getUrlPattern(), CORS_PREFIX + "urlPattern"); + addContextProperty(context, corsProperties.getPreflightMaxAge(), CORS_PREFIX + "preflightMaxAge"); + addContextProperty(context, corsProperties.getRequestDecorate(), CORS_PREFIX + "requestDecorate"); } LabKeyServer.ServerSslProperties sslProps = _server.serverSslSource(); @@ -220,6 +255,14 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) return super.getTomcatWebServer(tomcat); } + private void addContextProperty(StandardContext context, String value, String name) + { + if (null != value) + { + context.addParameter(name, value); + } + } + // Issue 48565: allow for JSON-formatted access logs in embedded tomcat private void configureJsonAccessLogging(Tomcat tomcat, LabKeyServer.JsonAccessLog logConfig) {