From 6634b038ff54ed9f89b1c8a144cbf227929843c0 Mon Sep 17 00:00:00 2001 From: Gabriel Roldan Date: Thu, 6 Feb 2025 18:23:29 -0300 Subject: [PATCH] [GWC-1363] Support Environment Parametrization for WMSLayer Credentials This commit enhances security and configurability by enabling dynamic runtime resolution of HTTP Basic Authentication credentials for WMS layers. Credentials can now be injected from environment variables, reducing the need to hardcode sensitive values. This improves code maintainability, supports secure multi- environment deployments, and simplifies testing through dynamic configuration. 1. **Dynamic Environment Parametrization**: - Introduced `GeoWebCacheEnvironment#isAllowEnvParametrization()` to replace the static `ALLOW_ENV_PARAMETRIZATION` field, allowing runtime toggling. 2. **Environment Variable Resolution Refactor**: - Replaced direct static field checks with method calls. - Updated `resolveValue()` and related methods to use environment variables dynamically. 3. **WMS Credentials Management Update**: - Added `getResolvedHttpUsername()` and `getResolvedHttpPassword()` in `WMSHttpHelper`. - Created `setGeoWebCacheEnvironment()` for dependency injection. 4. **Testing Enhancements**: - Integrated the `system-rules` library for environment variable manipulation. - Added tests to cover default, custom, and parameterized credentials. 5. **Code Improvements**: - Replaced unsafe casts in `resolveValue()`. - Improved exception handling by switching from `Throwable` to `RuntimeException`. - Added better logging and documentation for credential handling. --- geowebcache/core/pom.xml | 6 + .../geowebcache/GeoWebCacheEnvironment.java | 36 ++- .../geowebcache/config/XMLConfiguration.java | 3 + .../geowebcache/layer/wms/WMSHttpHelper.java | 110 +++++++- .../GeoWebCacheEnvironmentTest.java | 2 +- .../EnvironmentAwareXMLConfigurationTest.java | 263 ++++++++++++++++++ .../layer/wms/WMSHttpHelperTest.java | 104 +++++++ .../config/geowebcache-config-env.xml | 61 ++++ .../geowebcache/diskquota/ConfigLoader.java | 4 +- .../diskquota/jdbc/JDBCConfiguration.java | 19 +- geowebcache/pom.xml | 7 + .../org/geowebcache/s3/S3BlobStoreInfo.java | 2 +- .../swift/SwiftBlobStoreConfigProvider.java | 6 +- 13 files changed, 578 insertions(+), 45 deletions(-) create mode 100644 geowebcache/core/src/test/java/org/geowebcache/config/EnvironmentAwareXMLConfigurationTest.java create mode 100644 geowebcache/core/src/test/java/org/geowebcache/layer/wms/WMSHttpHelperTest.java create mode 100644 geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache-config-env.xml diff --git a/geowebcache/core/pom.xml b/geowebcache/core/pom.xml index accff5c27..6036d9837 100644 --- a/geowebcache/core/pom.xml +++ b/geowebcache/core/pom.xml @@ -205,6 +205,12 @@ awaitility test + + + com.github.stefanbirkner + system-rules + test + diff --git a/geowebcache/core/src/main/java/org/geowebcache/GeoWebCacheEnvironment.java b/geowebcache/core/src/main/java/org/geowebcache/GeoWebCacheEnvironment.java index e3c7ba4bd..6ab8cbe74 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/GeoWebCacheEnvironment.java +++ b/geowebcache/core/src/main/java/org/geowebcache/GeoWebCacheEnvironment.java @@ -23,7 +23,6 @@ import org.springframework.beans.factory.config.PlaceholderConfigurerSupport; import org.springframework.core.Constants; import org.springframework.util.PropertyPlaceholderHelper; -import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; /** * Utility class uses to process GeoWebCache configuration workflow through external environment variables. @@ -46,7 +45,7 @@ public class GeoWebCacheEnvironment { /** logger */ - public static Logger LOGGER = Logging.getLogger(GeoWebCacheEnvironment.class.getName()); + public static final Logger LOGGER = Logging.getLogger(GeoWebCacheEnvironment.class.getName()); private static final Constants constants = new Constants(PlaceholderConfigurerSupport.class); @@ -55,9 +54,13 @@ public class GeoWebCacheEnvironment { * placeholders translation. * *

Default to FALSE + * + * @deprecated a static final variable does prevents change during runtime and hinders testing. Use + * {@link #isAllowEnvParametrization()} instead. */ + @Deprecated(forRemoval = true) public static final boolean ALLOW_ENV_PARAMETRIZATION = - Boolean.valueOf(GeoWebCacheExtensions.getProperty("ALLOW_ENV_PARAMETRIZATION")); + Boolean.parseBoolean(GeoWebCacheExtensions.getProperty("ALLOW_ENV_PARAMETRIZATION")); private static final String nullValue = "null"; @@ -67,10 +70,17 @@ public class GeoWebCacheEnvironment { constants.asString("DEFAULT_VALUE_SEPARATOR"), true); - private final PlaceholderResolver resolver = placeholderName -> resolvePlaceholder(placeholderName); - private Properties props; + /** + * Determines if the {@code ALLOW_ENV_PARAMETRIZATION} environment variable is set to {@code true} and hence + * variable variable substitution of configuration parameters using ${} place holders can be performed + * through {@link #resolveValue(Object)}. + */ + public boolean isAllowEnvParametrization() { + return Boolean.parseBoolean(GeoWebCacheExtensions.getProperty("ALLOW_ENV_PARAMETRIZATION")); + } + /** * Internal "props" getter method. * @@ -107,7 +117,7 @@ protected String resolveSystemProperty(String key) { value = System.getenv(key); } return value; - } catch (Throwable ex) { + } catch (RuntimeException ex) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Could not access system property '" + key + "': " + ex); } @@ -116,7 +126,7 @@ protected String resolveSystemProperty(String key) { } protected String resolveStringValue(String strVal) throws BeansException { - String resolved = this.helper.replacePlaceholders(strVal, this.resolver); + String resolved = this.helper.replacePlaceholders(strVal, this::resolvePlaceholder); return (resolved.equals(nullValue) ? null : resolved); } @@ -127,19 +137,17 @@ protected String resolveStringValue(String strVal) throws BeansException { *

The method first looks for System variables which take precedence on local ones, then into internal props * injected through the applicationContext. */ - public Object resolveValue(Object value) { - if (value != null) { - if (value instanceof String) { - return resolveStringValue((String) value); - } + @SuppressWarnings("unchecked") + public T resolveValue(T value) { + if (value instanceof String) { + return (T) resolveStringValue((String) value); } return value; } private String resolveValueIfEnabled(String value) { - if (ALLOW_ENV_PARAMETRIZATION) return (String) resolveValue(value); - else return value; + return isAllowEnvParametrization() ? resolveValue(value) : value; } private boolean validateBoolean(String value) { diff --git a/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java b/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java index 6206baaa3..d320c71aa 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java +++ b/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java @@ -53,6 +53,7 @@ import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.geotools.util.logging.Logging; +import org.geowebcache.GeoWebCacheEnvironment; import org.geowebcache.GeoWebCacheException; import org.geowebcache.GeoWebCacheExtensions; import org.geowebcache.config.ContextualConfigurationProvider.Context; @@ -282,6 +283,8 @@ public void setDefaultValues(TileLayer layer) { sourceHelper = new WMSHttpHelper(null, null, proxyUrl); log.fine("Not using HTTP credentials for " + wl.getName()); } + GeoWebCacheEnvironment gwcEnv = GeoWebCacheExtensions.bean(GeoWebCacheEnvironment.class); + sourceHelper.setGeoWebCacheEnvironment(gwcEnv); wl.setSourceHelper(sourceHelper); wl.setLockProvider(getGwcConfig().getLockProvider()); diff --git a/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSHttpHelper.java b/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSHttpHelper.java index cdc4faae6..c5b943877 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSHttpHelper.java +++ b/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSHttpHelper.java @@ -13,6 +13,7 @@ */ package org.geowebcache.layer.wms; +import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -29,6 +30,7 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; +import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -36,7 +38,9 @@ import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.geotools.util.logging.Logging; +import org.geowebcache.GeoWebCacheEnvironment; import org.geowebcache.GeoWebCacheException; +import org.geowebcache.GeoWebCacheExtensions; import org.geowebcache.io.Resource; import org.geowebcache.layer.TileResponseReceiver; import org.geowebcache.mime.ErrorMime; @@ -48,17 +52,39 @@ import org.geowebcache.util.URLs; import org.springframework.util.Assert; -/** This class is a wrapper for HTTP interaction with WMS backend */ +/** + * Helper class for HTTP interactions of {@link WMSLayer} with a WMS backend. + * + *

HTTP Basic Auth username and password supplied to the constructor support environment parametrization. + * + * @see GeoWebCacheEnvironment#isAllowEnvParametrization() + * @see GeoWebCacheEnvironment#resolveValue(Object) + */ public class WMSHttpHelper extends WMSSourceHelper { private static final Logger log = Logging.getLogger(WMSHttpHelper.class.getName()); + /** + * Used by {@link #getResolvedHttpUsername()} and {@link #getResolvedHttpPassword()} to + * {@link GeoWebCacheEnvironment#resolveValue resolve} the actual values against environment variables when + * {@link GeoWebCacheEnvironment#isAllowEnvParametrization() ALLOW_ENV_PARAMETRIZATION} is enabled. + */ + protected GeoWebCacheEnvironment gwcEnv; + private final URL proxyUrl; + /** + * HTTP Basic Auth username, might be de-referenced using environment variable substitution. Always access it + * through {@link #getResolvedHttpUsername()} + */ private final String httpUsername; + /** + * HTTP Basic Auth password, might be de-referenced using environment variable substitution. Always access it + * through {@link #getResolvedHttpUsername()} + */ private final String httpPassword; - protected volatile HttpClient client; + protected HttpClient client; public WMSHttpHelper() { this(null, null, null); @@ -71,23 +97,75 @@ public WMSHttpHelper(String httpUsername, String httpPassword, URL proxyUrl) { this.proxyUrl = proxyUrl; } - HttpClient getHttpClient() { - if (client == null) { - synchronized (this) { - if (client != null) { - return client; - } + /** + * Used by {@link #executeRequest} + * + * @return the actual http username to use when executing requests + */ + public String getResolvedHttpUsername() { + return resolvePlaceHolders(httpUsername); + } + + /** + * Used by {@link #executeRequest} + * + * @return the actual http password to use when executing requests + */ + public String getResolvedHttpPassword() { + return resolvePlaceHolders(httpPassword); + } + + /** + * Assigns the environment variable {@link GeoWebCacheEnvironment#resolveValue resolver} to perform variable + * substitution against the configured http username and password during {@link #executeRequest} + * + *

When unset, a bean of this type will be looked up through {@link GeoWebCacheExtensions#bean(Class)} + */ + public void setGeoWebCacheEnvironment(GeoWebCacheEnvironment gwcEnv) { + this.gwcEnv = gwcEnv; + } - HttpClientBuilder builder = new HttpClientBuilder( - null, getBackendTimeout(), httpUsername, httpPassword, proxyUrl, getConcurrency()); + private String resolvePlaceHolders(String value) { + GeoWebCacheEnvironment env = getEnvironment(); + return env != null && env.isAllowEnvParametrization() ? env.resolveValue(value) : value; + } - client = builder.buildClient(); - } + private GeoWebCacheEnvironment getEnvironment() { + GeoWebCacheEnvironment env = this.gwcEnv; + if (env == null) { + env = GeoWebCacheExtensions.bean(GeoWebCacheEnvironment.class); + this.gwcEnv = env; } + return env; + } + /** + * Note: synchronizing the entire method avoids double-checked locking altogether. Modern JVMs optimize this and + * reduce the overhead of synchronization. Otherwise PMD complains with a `Double checked locking is not thread safe + * in Java.` error. + */ + synchronized HttpClient getHttpClient() { + if (client == null) { + int backendTimeout = getBackendTimeout(); + String user = getResolvedHttpUsername(); + String password = getResolvedHttpPassword(); + URL proxy = proxyUrl; + int concurrency = getConcurrency(); + client = buildHttpClient(backendTimeout, user, password, proxy, concurrency); + } return client; } + @VisibleForTesting + HttpClient buildHttpClient(int backendTimeout, String username, String password, URL proxy, int concurrency) { + + URL serverUrl = null; + HttpClientBuilder builder = + new HttpClientBuilder(serverUrl, backendTimeout, username, password, proxy, concurrency); + + return builder.buildClient(); + } + /** Loops over the different backends, tries the request */ @Override protected void makeRequest( @@ -319,7 +397,13 @@ public HttpResponse executeRequest( if (log.isLoggable(Level.FINER)) { log.finer(method.toString()); } - return getHttpClient().execute(method); + HttpClient httpClient = getHttpClient(); + return execute(httpClient, method); + } + + @VisibleForTesting + HttpResponse execute(HttpClient httpClient, HttpRequestBase method) throws IOException, ClientProtocolException { + return httpClient.execute(method); } private String processRequestParameters(Map parameters) throws UnsupportedEncodingException { diff --git a/geowebcache/core/src/test/java/org/geowebcache/GeoWebCacheEnvironmentTest.java b/geowebcache/core/src/test/java/org/geowebcache/GeoWebCacheEnvironmentTest.java index 210688586..d9840ab6f 100644 --- a/geowebcache/core/src/test/java/org/geowebcache/GeoWebCacheEnvironmentTest.java +++ b/geowebcache/core/src/test/java/org/geowebcache/GeoWebCacheEnvironmentTest.java @@ -58,7 +58,7 @@ public void testEnvironment() { Assert.assertEquals(1, extensions.size()); Assert.assertTrue(extensions.contains(genv)); - Assert.assertTrue(GeoWebCacheEnvironment.ALLOW_ENV_PARAMETRIZATION); + Assert.assertTrue(genv.isAllowEnvParametrization()); } @Test diff --git a/geowebcache/core/src/test/java/org/geowebcache/config/EnvironmentAwareXMLConfigurationTest.java b/geowebcache/core/src/test/java/org/geowebcache/config/EnvironmentAwareXMLConfigurationTest.java new file mode 100644 index 000000000..77ab90fb8 --- /dev/null +++ b/geowebcache/core/src/test/java/org/geowebcache/config/EnvironmentAwareXMLConfigurationTest.java @@ -0,0 +1,263 @@ +/** + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + *

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see + * . + */ +package org.geowebcache.config; + +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import org.geowebcache.GeoWebCacheEnvironment; +import org.geowebcache.GeoWebCacheException; +import org.geowebcache.GeoWebCacheExtensions; +import org.geowebcache.grid.GridSetBroker; +import org.geowebcache.layer.wms.WMSHttpHelper; +import org.geowebcache.layer.wms.WMSLayer; +import org.geowebcache.layer.wms.WMSSourceHelper; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.EnvironmentVariables; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; + +public class EnvironmentAwareXMLConfigurationTest { + /** Allows to set environment variables for each individual test */ + @Rule + public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); + + /** Loaded from {@literal geowebcache-config-env.xml}, defines the {@link WMSLayer}s below */ + private XMLConfiguration xmlConfig; + + /** Test layer with http user/pwd set from global config */ + private WMSLayer withDefaultCredentials; + + /** Test layer with http user/pwd defined as env variable placeholders */ + private WMSLayer withEnvVariableCredentials; + + /** Test layer with its own, non parametrized http user/pwd */ + private WMSLayer withCustomCredentials; + + /** + * Test layer with its own, non parametrized http user/pwd, where their values contain the '${' placeholder prefix + */ + private WMSLayer customCredentialsWithEnvPrefix; + + /** + * Make {@code GeoWebCacheExtensions.bean(GeoWebCacheEnvironment.class)} return a bean, for + * {@link XMLConfiguration#setDefaultValues(TileLayer)} to set it on each + * {@link WMSHttpHelper#setGeoWebCacheEnvironment(GeoWebCacheEnvironment)} + */ + @BeforeClass + public static void setUpAppContext() { + GeoWebCacheEnvironment gwcEnv = new GeoWebCacheEnvironment(); + ApplicationContext appContext = mock(ApplicationContext.class); + + when(appContext.getBeansOfType(GeoWebCacheEnvironment.class)) + .thenReturn(Map.of("geoWebCacheEnvironment", gwcEnv)); + when(appContext.getBean("geoWebCacheEnvironment")).thenReturn(gwcEnv); + new GeoWebCacheExtensions().setApplicationContext(appContext); + } + + /** + * Set up the following environment variables and load the test layers from {@literal geowebcache-config-env.xml}: + * + *

+ */ + @Before + public void setUp() throws URISyntaxException, GeoWebCacheException { + + setEnvironmentVariablesForCredentials(); + + xmlConfig = loadConfig(); + withDefaultCredentials = getLayer("default_credentials"); + withEnvVariableCredentials = getLayer("env_var_credentials"); + withCustomCredentials = getLayer("custom_credentials"); + customCredentialsWithEnvPrefix = getLayer("custom_credentials_with_env_prefix"); + } + + private void setEnvironmentVariablesForCredentials() { + environmentVariables.set("DEFAULT_USER", "default_user_value"); + environmentVariables.set("DEFAULT_SECRET", "default_secret_value"); + environmentVariables.set("CUSTOM_USER", "custom_user_value"); + environmentVariables.set("CUSTOM_SECRET", "custom_secret_value"); + } + + private void enableEnvParametrization() { + environmentVariables.set("ALLOW_ENV_PARAMETRIZATION", "true"); + } + + private WMSLayer getLayer(String layerName) { + return (WMSLayer) xmlConfig.getLayer(layerName).orElseThrow(); + } + + private XMLConfiguration loadConfig() throws GeoWebCacheException, URISyntaxException { + File cpDirectory = + new File(requireNonNull(this.getClass().getResource("./")).toURI()); + XMLFileResourceProvider resourceProvider = new XMLFileResourceProvider( + "geowebcache-config-env.xml", (WebApplicationContext) null, cpDirectory.getAbsolutePath(), null); + + XMLConfiguration config = new XMLConfiguration(null, resourceProvider); + config.setGridSetBroker(new GridSetBroker(List.of(new DefaultGridsets(true, true)))); + config.afterPropertiesSet(); + return config; + } + + @Test + public void testLayerWithDefaultCredentialsAllowEnvDisabled() { + assertCredentials(withDefaultCredentials, null, null, "layer loading shall not change the layer config"); + + xmlConfig.setDefaultValues(withDefaultCredentials); + assertCredentials( + withDefaultCredentials, null, null, "setting default values shall not change the layer config"); + + assertCredentials( + withDefaultCredentials.getSourceHelper(), + "${DEFAULT_USER}", + "${DEFAULT_SECRET}", + "sourceHelper should be assigned the default user and password, no env var substitution expected"); + } + + @Test + public void testLayerWithDefaultCredentialsAllowEnvEnabled() { + enableEnvParametrization(); + + assertCredentials(withDefaultCredentials, null, null, "layer loading shall not change the layer config"); + + xmlConfig.setDefaultValues(withDefaultCredentials); + assertCredentials( + withDefaultCredentials, null, null, "setting default values shall not change the layer config"); + + assertCredentials( + withDefaultCredentials.getSourceHelper(), + "default_user_value", + "default_secret_value", + "sourceHelper should have resolved the global user and password"); + } + + @Test + public void testLayerWithEnvVarCredentialsAllowEnvDisabled() { + assertCredentials( + withEnvVariableCredentials, + "${CUSTOM_USER}", + "${CUSTOM_SECRET}", + "layer loading shall not change the layer config"); + + xmlConfig.setDefaultValues(withEnvVariableCredentials); + assertCredentials( + withEnvVariableCredentials, + "${CUSTOM_USER}", + "${CUSTOM_SECRET}", + "setting default values shall not change the layer config"); + + assertCredentials( + withEnvVariableCredentials.getSourceHelper(), + "${CUSTOM_USER}", + "${CUSTOM_SECRET}", + "sourceHelper should keep the layer user and password, no env var substitution expected"); + } + + @Test + public void testLayerWithEnvVarCredentialsAllowEnvEnabled() { + enableEnvParametrization(); + + assertCredentials( + withEnvVariableCredentials, + "${CUSTOM_USER}", + "${CUSTOM_SECRET}", + "layer loading shall not change the layer config"); + + xmlConfig.setDefaultValues(withEnvVariableCredentials); + assertCredentials( + withEnvVariableCredentials, + "${CUSTOM_USER}", + "${CUSTOM_SECRET}", + "setting default values shall not change the layer config"); + + assertCredentials( + withEnvVariableCredentials.getSourceHelper(), + "custom_user_value", + "custom_secret_value", + "sourceHelper should have resolved the layer's user and password variables"); + } + + @Test + public void testLayerWithCustomCredentialsIsAllowEnvAgnostic() { + + assertLayerUnchanged(withCustomCredentials, "testuser", "testpass"); + + enableEnvParametrization(); + + assertLayerUnchanged(withCustomCredentials, "testuser", "testpass"); + } + + @Test + public void testLayerWithCustomCredentialsHavingPlaceholderPrefixIsAllowEnvAgnostic() { + + assertLayerUnchanged(customCredentialsWithEnvPrefix, "${user", "pass${word"); + + enableEnvParametrization(); + + assertLayerUnchanged(customCredentialsWithEnvPrefix, "${user", "pass${word"); + } + + public void assertLayerUnchanged(WMSLayer staticCredentials, String user, String password) { + assertCredentials( + staticCredentials, + user, + password, + "parametrization shouldn't affect a non parametrized layer's credentials"); + + xmlConfig.setDefaultValues(staticCredentials); + assertCredentials( + staticCredentials, + user, + password, + "set default values shouldn't affect a non parametrized layer's credenals"); + + assertCredentials( + (WMSHttpHelper) staticCredentials.getSourceHelper(), + user, + password, + "set default values shouldn't affect a non parametrized layer's credenals"); + + WMSHttpHelper sourceHelper = (WMSHttpHelper) staticCredentials.getSourceHelper(); + assertCredentials( + sourceHelper, + user, + password, + "source helper shouldn't not change credential values on a non parametrized layer"); + } + + private void assertCredentials( + WMSSourceHelper wmsSourceHelper, String expectedUser, String expectedPassword, String message) { + + WMSHttpHelper helper = (WMSHttpHelper) wmsSourceHelper; + assertEquals(message, expectedUser, helper.getResolvedHttpUsername()); + assertEquals(message, expectedPassword, helper.getResolvedHttpPassword()); + } + + private void assertCredentials(WMSLayer layer, String expectedUser, String expectedPassword, String message) { + assertEquals(message, expectedUser, layer.getHttpUsername()); + assertEquals(message, expectedPassword, layer.getHttpPassword()); + } +} diff --git a/geowebcache/core/src/test/java/org/geowebcache/layer/wms/WMSHttpHelperTest.java b/geowebcache/core/src/test/java/org/geowebcache/layer/wms/WMSHttpHelperTest.java new file mode 100644 index 000000000..7ca14cfb1 --- /dev/null +++ b/geowebcache/core/src/test/java/org/geowebcache/layer/wms/WMSHttpHelperTest.java @@ -0,0 +1,104 @@ +/** + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General + * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + *

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see + * . + */ +package org.geowebcache.layer.wms; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; +import org.geowebcache.GeoWebCacheEnvironment; +import org.geowebcache.layer.wms.WMSLayer.HttpRequestMode; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.EnvironmentVariables; +import org.mockito.Mockito; + +public class WMSHttpHelperTest { + + /** Allows to set environment variables for each individual test */ + @Rule + public final EnvironmentVariables environmentVariables = new EnvironmentVariables(); + + private WMSHttpHelper wmsHelper(String username, String password) { + WMSHttpHelper wmsHelper = new WMSHttpHelper(username, password, null); + wmsHelper.setGeoWebCacheEnvironment(new GeoWebCacheEnvironment()); + return wmsHelper; + } + + private void enableEnvParametrization(boolean enable) { + environmentVariables.set("ALLOW_ENV_PARAMETRIZATION", String.valueOf(enable)); + } + + @Test + public void testNoGeoWebCacheEnvironmentSet() { + WMSHttpHelper helper = wmsHelper("u${ername}", "pas${word}"); + helper.setGeoWebCacheEnvironment(null); + assertEquals("u${ername}", helper.getResolvedHttpUsername()); + assertEquals("pas${word}", helper.getResolvedHttpPassword()); + } + + @Test + public void testCredentialsUnchangedWhenEnvParametrizationDisabled() { + WMSHttpHelper helper = wmsHelper("u${ername}", "pas${word}"); + assertEquals("u${ername}", helper.getResolvedHttpUsername()); + assertEquals("pas${word}", helper.getResolvedHttpPassword()); + + enableEnvParametrization(false); + assertEquals("u${ername}", helper.getResolvedHttpUsername()); + assertEquals("pas${word}", helper.getResolvedHttpPassword()); + } + + @Test + public void testCredentialsResolvedWhenEnvParametrizationEnabled() { + WMSHttpHelper helper = wmsHelper("${GWC_USER}", "${GWC_PWD}"); + + enableEnvParametrization(true); + assertEquals("${GWC_USER}", helper.getResolvedHttpUsername()); + assertEquals("${GWC_PWD}", helper.getResolvedHttpPassword()); + + environmentVariables.set("GWC_USER", "user_resolved"); + environmentVariables.set("GWC_PWD", "pwd_resolved"); + + assertEquals("user_resolved", helper.getResolvedHttpUsername()); + assertEquals("pwd_resolved", helper.getResolvedHttpPassword()); + } + + @Test + public void testExecuteRequestUsesResolvedCredentials() throws IOException, URISyntaxException { + enableEnvParametrization(true); + environmentVariables.set("GWC_USER", "user_resolved"); + environmentVariables.set("GWC_PWD", "pwd_resolved"); + + WMSHttpHelper helper = spy(wmsHelper("${GWC_USER}", "${GWC_PWD}")); + // do not actually execute the request + doReturn(null).when(helper).execute(any(HttpClient.class), any(HttpRequestBase.class)); + + // just check the http client is built with the resolved credeltials + URL url = new URI("http://example.com/wms?request=getcapabilities").toURL(); + helper.executeRequest(url, null, 0, HttpRequestMode.Get); + verify(helper) + .buildHttpClient( + Mockito.anyInt(), + Mockito.eq("user_resolved"), + Mockito.eq("pwd_resolved"), + Mockito.nullable(URL.class), + Mockito.anyInt()); + } +} diff --git a/geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache-config-env.xml b/geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache-config-env.xml new file mode 100644 index 000000000..01e9f8b2d --- /dev/null +++ b/geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache-config-env.xml @@ -0,0 +1,61 @@ + + + + 1.8.0 + ${DEFAULT_USER} + ${DEFAULT_SECRET} + + + + default_credentials + + Layer with default http user and pwd + + + https://example.com/geoserver/wms + + + + + custom_credentials + + Layer with non-parameterized custom http user and pwd + + + https://example.com/geoserver/wms + + testuser + testpass + + + + env_var_credentials + + Layer with parameterized custom http user and pwd + + + https://example.com/geoserver/wms + + ${CUSTOM_USER} + ${CUSTOM_SECRET} + + + + custom_credentials_with_env_prefix + + Layer with non-parameterized http user and pwd that contain the ${ placeholder prefix + + + https://example.com/geoserver/wms + + ${user + pass${word + + + + \ No newline at end of file diff --git a/geowebcache/diskquota/core/src/main/java/org/geowebcache/diskquota/ConfigLoader.java b/geowebcache/diskquota/core/src/main/java/org/geowebcache/diskquota/ConfigLoader.java index cf7e01b8d..4cc13dfa7 100644 --- a/geowebcache/diskquota/core/src/main/java/org/geowebcache/diskquota/ConfigLoader.java +++ b/geowebcache/diskquota/core/src/main/java/org/geowebcache/diskquota/ConfigLoader.java @@ -266,8 +266,8 @@ public static DiskQuotaConfig loadConfiguration(final Reader reader, XStream xst final GeoWebCacheEnvironment gwcEnvironment = GeoWebCacheExtensions.bean(GeoWebCacheEnvironment.class); - if (gwcEnvironment != null && GeoWebCacheEnvironment.ALLOW_ENV_PARAMETRIZATION) { - fromXML.setQuotaStore((String) gwcEnvironment.resolveValue(fromXML.getQuotaStore())); + if (gwcEnvironment != null && gwcEnvironment.isAllowEnvParametrization()) { + fromXML.setQuotaStore(gwcEnvironment.resolveValue(fromXML.getQuotaStore())); } return fromXML; diff --git a/geowebcache/diskquota/jdbc/src/main/java/org/geowebcache/diskquota/jdbc/JDBCConfiguration.java b/geowebcache/diskquota/jdbc/src/main/java/org/geowebcache/diskquota/jdbc/JDBCConfiguration.java index 9247b9444..9c92110dd 100644 --- a/geowebcache/diskquota/jdbc/src/main/java/org/geowebcache/diskquota/jdbc/JDBCConfiguration.java +++ b/geowebcache/diskquota/jdbc/src/main/java/org/geowebcache/diskquota/jdbc/JDBCConfiguration.java @@ -397,21 +397,18 @@ public JDBCConfiguration clone(boolean allowEnvParametrization) { final GeoWebCacheEnvironment gwcEnvironment = GeoWebCacheExtensions.bean(GeoWebCacheEnvironment.class); - if (allowEnvParametrization && gwcEnvironment != null && GeoWebCacheEnvironment.ALLOW_ENV_PARAMETRIZATION) { - conf.setDialect((String) gwcEnvironment.resolveValue(getDialect())); - conf.setJNDISource((String) gwcEnvironment.resolveValue(getJNDISource())); + if (allowEnvParametrization && gwcEnvironment != null && gwcEnvironment.isAllowEnvParametrization()) { + conf.setDialect(gwcEnvironment.resolveValue(getDialect())); + conf.setJNDISource(gwcEnvironment.resolveValue(getJNDISource())); ConnectionPoolConfiguration connectionPoolConfig = getConnectionPool(); if (connectionPoolConfig != null) { ConnectionPoolConfiguration expConnectionPoolConfig = SerializationUtils.clone(connectionPoolConfig); - expConnectionPoolConfig.setDriver( - (String) gwcEnvironment.resolveValue(connectionPoolConfig.getDriver())); - expConnectionPoolConfig.setUrl((String) gwcEnvironment.resolveValue(connectionPoolConfig.getUrl())); - expConnectionPoolConfig.setUsername( - (String) gwcEnvironment.resolveValue(connectionPoolConfig.getUsername())); - expConnectionPoolConfig.setPassword( - (String) gwcEnvironment.resolveValue(connectionPoolConfig.getPassword())); + expConnectionPoolConfig.setDriver(gwcEnvironment.resolveValue(connectionPoolConfig.getDriver())); + expConnectionPoolConfig.setUrl(gwcEnvironment.resolveValue(connectionPoolConfig.getUrl())); + expConnectionPoolConfig.setUsername(gwcEnvironment.resolveValue(connectionPoolConfig.getUsername())); + expConnectionPoolConfig.setPassword(gwcEnvironment.resolveValue(connectionPoolConfig.getPassword())); expConnectionPoolConfig.setValidationQuery( - (String) gwcEnvironment.resolveValue(connectionPoolConfig.getValidationQuery())); + gwcEnvironment.resolveValue(connectionPoolConfig.getValidationQuery())); conf.setConnectionPool(expConnectionPoolConfig); } diff --git a/geowebcache/pom.xml b/geowebcache/pom.xml index a7aa88cec..8cb17725b 100644 --- a/geowebcache/pom.xml +++ b/geowebcache/pom.xml @@ -493,6 +493,13 @@ jackson-core ${jackson.version} + + + com.github.stefanbirkner + system-rules + 1.19.0 + test + diff --git a/geowebcache/s3storage/src/main/java/org/geowebcache/s3/S3BlobStoreInfo.java b/geowebcache/s3storage/src/main/java/org/geowebcache/s3/S3BlobStoreInfo.java index 909bfb956..998911cc2 100644 --- a/geowebcache/s3storage/src/main/java/org/geowebcache/s3/S3BlobStoreInfo.java +++ b/geowebcache/s3storage/src/main/java/org/geowebcache/s3/S3BlobStoreInfo.java @@ -365,7 +365,7 @@ private String nullSafeResolveString(String value, GeoWebCacheEnvironment gwcEnv if (value == null) { return null; } - return gwcEnvironment.resolveValue(value).toString(); + return gwcEnvironment.resolveValue(value); } private Integer toInteger(String value) { diff --git a/geowebcache/swiftblob/src/main/java/org/geowebcache/swift/SwiftBlobStoreConfigProvider.java b/geowebcache/swiftblob/src/main/java/org/geowebcache/swift/SwiftBlobStoreConfigProvider.java index ce80e320b..57a681f06 100644 --- a/geowebcache/swiftblob/src/main/java/org/geowebcache/swift/SwiftBlobStoreConfigProvider.java +++ b/geowebcache/swiftblob/src/main/java/org/geowebcache/swift/SwiftBlobStoreConfigProvider.java @@ -60,12 +60,12 @@ private static String resolveFromEnv(String str) { if (gwcEnvironment == null) { gwcEnvironment = GeoWebCacheExtensions.bean(GeoWebCacheEnvironment.class); } - if (gwcEnvironment != null && str != null && GeoWebCacheEnvironment.ALLOW_ENV_PARAMETRIZATION) { - Object result = gwcEnvironment.resolveValue(str); + if (gwcEnvironment != null && str != null && gwcEnvironment.isAllowEnvParametrization()) { + String result = gwcEnvironment.resolveValue(str); if (result == null) { return null; } - return result.toString(); + return result; } return str; }