Restrict access to environment variables when at the server runtime

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-12-11 08:42:43 -03:00 committed by Bruno Oliveira da Silva
parent edbf75e6ee
commit 7a76858fe4
26 changed files with 269 additions and 159 deletions

View File

@ -19,6 +19,7 @@ package org.keycloak.adapters.saml.config.parsers;
import org.keycloak.adapters.saml.config.Key;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.util.StaxParserUtil;
@ -60,20 +61,24 @@ public class KeyParser extends AbstractKeycloakSamlAdapterV1Parser<Key> {
case CERTIFICATE_PEM:
StaxParserUtil.advance(xmlEventReader);
value = StaxParserUtil.getElementText(xmlEventReader);
target.setCertificatePem(StringPropertyReplacer.replaceProperties(value));
target.setCertificatePem(replaceProperties(value));
break;
case PUBLIC_KEY_PEM:
StaxParserUtil.advance(xmlEventReader);
value = StaxParserUtil.getElementText(xmlEventReader);
target.setPublicKeyPem(StringPropertyReplacer.replaceProperties(value));
target.setPublicKeyPem(replaceProperties(value));
break;
case PRIVATE_KEY_PEM:
StaxParserUtil.advance(xmlEventReader);
value = StaxParserUtil.getElementText(xmlEventReader);
target.setPrivateKeyPem(StringPropertyReplacer.replaceProperties(value));
target.setPrivateKeyPem(replaceProperties(value));
break;
}
}
private String replaceProperties(String value) {
return StringPropertyReplacer.replaceProperties(value, SystemEnvProperties.UNFILTERED::getProperty);
}
}

View File

@ -21,7 +21,6 @@ import static org.keycloak.constants.ServiceUrlConstants.AUTHZ_DISCOVERY_URL;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -33,7 +32,6 @@ import org.keycloak.authorization.client.util.TokenCallable;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.SystemPropertiesJsonParserFactory;
/**
* <p>This is class serves as an entry point for clients looking for access to Keycloak Authorization Services.

View File

@ -15,7 +15,11 @@
* limitations under the License.
*/
package org.keycloak.util;
package org.keycloak.authorization.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.io.IOContext;
@ -24,19 +28,12 @@ import com.fasterxml.jackson.databind.MappingJsonFactory;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
/**
* Provides replacing of system properties for parsed values
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SystemPropertiesJsonParserFactory extends MappingJsonFactory {
private static final Properties properties = new SystemEnvProperties();
class SystemPropertiesJsonParserFactory extends MappingJsonFactory {
@Override
protected JsonParser _createParser(InputStream in, IOContext ctxt) throws IOException {
@ -71,7 +68,7 @@ public class SystemPropertiesJsonParserFactory extends MappingJsonFactory {
@Override
public String getText() throws IOException {
String orig = super.getText();
return StringPropertyReplacer.replaceProperties(orig, properties);
return StringPropertyReplacer.replaceProperties(orig, SystemEnvProperties.UNFILTERED::getProperty);
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.authorization.client;
import java.io.IOException;
import java.io.InputStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.adapters.config.AdapterConfig;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JsonParserTest {
@Test
public void testParsingSystemProps() throws IOException {
System.setProperty("my.host", "foo");
System.setProperty("con.pool.size", "200");
System.setProperty("allow.any.hostname", "true");
System.setProperty("socket.timeout.millis", "6000");
System.setProperty("connection.timeout.millis", "7000");
System.setProperty("connection.ttl.millis", "500");
InputStream is = getClass().getClassLoader().getResourceAsStream("keycloak.json");
ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
AdapterConfig config = mapper.readValue(is, AdapterConfig.class);
Assert.assertEquals("http://foo:8080/auth", config.getAuthServerUrl());
Assert.assertEquals("external", config.getSslRequired());
Assert.assertEquals("angular-product${non.existing}", config.getResource());
Assert.assertTrue(config.isPublicClient());
Assert.assertTrue(config.isAllowAnyHostname());
Assert.assertEquals(100, config.getCorsMaxAge());
Assert.assertEquals(200, config.getConnectionPoolSize());
Assert.assertEquals(6000L, config.getSocketTimeout());
Assert.assertEquals(7000L, config.getConnectionTimeout());
Assert.assertEquals(500L, config.getConnectionTTL());
}
}

View File

@ -0,0 +1,12 @@
{
"auth-server-url" : "http://${my.host}:8080/auth",
"ssl-required" : "external",
"resource" : "angular-product${non.existing}",
"public-client" : true,
"allow-any-hostname": "${allow.any.hostname}",
"cors-max-age": 100,
"connection-pool-size": "${con.pool.size}",
"socket-timeout-millis": "${socket.timeout.millis}",
"connection-timeout-millis": "${connection.timeout.millis}",
"connection-ttl-millis": "${connection.ttl.millis}"
}

View File

@ -17,22 +17,20 @@
package org.keycloak.common.util;
import java.io.File;
import java.util.Properties;
import java.util.Optional;
/**
* A utility class for replacing properties in strings.
* A utility class for replacing properties in strings.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="Scott.Stark@jboss.org">Scott Stark</a>
* @author <a href="claudio.vesco@previnet.it">Claudio Vesco</a>
* @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @version <tt>$Revision: 2898 $</tt>
* @version <tt>$Revision: 2898 $</tt>
*/
public final class StringPropertyReplacer
{
/** New line string constant */
public static final String NEWLINE = System.getProperty("line.separator", "\n");
/** File separator value */
private static final String FILE_SEPARATOR = File.separator;
@ -51,7 +49,12 @@ public final class StringPropertyReplacer
private static final int SEEN_DOLLAR = 1;
private static final int IN_BRACKET = 2;
private static final Properties systemEnvProperties = new SystemEnvProperties();
private static final PropertyResolver NULL_RESOLVER = property -> null;
private static PropertyResolver DEFAULT_PROPERTY_RESOLVER;
public static void setDefaultPropertyResolver(PropertyResolver systemVariables) {
DEFAULT_PROPERTY_RESOLVER = systemVariables;
}
/**
* Go through the input string and replace any occurrence of ${p} with
@ -72,14 +75,13 @@ public final class StringPropertyReplacer
* @return the input string with all property references replaced if any.
* If there are no valid references the input string will be returned.
*/
public static String replaceProperties(final String string)
{
return replaceProperties(string, (Properties) null);
public static String replaceProperties(final String string) {
return replaceProperties(string, getDefaultPropertyResolver());
}
/**
* Go through the input string and replace any occurrence of ${p} with
* the props.getProperty(p) value. If there is no such property p defined,
* the value resolves from {@code resolver}. If there is no such property p defined,
* then the ${p} reference will remain unchanged.
*
* If the property reference is of the form ${p:v} and there is no such property p,
@ -93,17 +95,10 @@ public final class StringPropertyReplacer
* value and the property ${:} is replaced with System.getProperty("path.separator").
*
* @param string - the string with possible ${} references
* @param props - the source for ${x} property ref values, null means use System.getProperty()
* @param resolver - the property resolver
* @return the input string with all property references replaced if any.
* If there are no valid references the input string will be returned.
*/
public static String replaceProperties(final String string, final Properties props) {
if (props == null) {
return replaceProperties(string, (PropertyResolver) null);
}
return replaceProperties(string, props::getProperty);
}
public static String replaceProperties(final String string, PropertyResolver resolver)
{
if(string == null) {
@ -171,10 +166,7 @@ public final class StringPropertyReplacer
else
{
// check from the properties
if (resolver != null)
value = resolver.resolve(key);
else
value = systemEnvProperties.getProperty(key);
value = resolveValue(resolver, key);
if (value == null)
{
@ -183,10 +175,7 @@ public final class StringPropertyReplacer
if (colon > 0)
{
String realKey = key.substring(0, colon);
if (resolver != null)
value = resolver.resolve(realKey);
else
value = systemEnvProperties.getProperty(realKey);
value = resolveValue(resolver, realKey);
if (value == null)
{
@ -239,7 +228,7 @@ public final class StringPropertyReplacer
throw new IllegalStateException("Infinite recursion happening when replacing properties on '" + buffer + "'");
}
}
// Done
return buffer.toString();
}
@ -257,26 +246,32 @@ public final class StringPropertyReplacer
{
// Check the first part
String key1 = key.substring(0, comma);
if (resolver != null)
value = resolver.resolve(key1);
else
value = systemEnvProperties.getProperty(key1);
value = resolveValue(resolver, key1);
}
// Check the second part, if there is one and first lookup failed
if (value == null && comma < key.length() - 1)
{
String key2 = key.substring(comma + 1);
if (resolver != null)
value = resolver.resolve(key2);
else
value = systemEnvProperties.getProperty(key2);
value = resolveValue(resolver, key2);
}
}
// Return whatever we've found or null
return value;
}
public interface PropertyResolver {
String resolve(String property);
}
private static String resolveValue(PropertyResolver resolver, String key) {
if (resolver == null) {
return getDefaultPropertyResolver().resolve(key);
}
return resolver.resolve(key);
}
private static PropertyResolver getDefaultPropertyResolver() {
return Optional.ofNullable(DEFAULT_PROPERTY_RESOLVER).orElse(NULL_RESOLVER);
}
}

View File

@ -17,19 +17,53 @@
package org.keycloak.common.util;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
/**
* <p>An utility class to resolve the value of a key based on the environment variables
* and system properties available at runtime. In most cases, you do not want to resolve whatever system variable is available at runtime but specify which ones
* can be used when resolving placeholders.
*
* <p>To resolve to an environment variable, the key must have a format like {@code env.<key>} where {@code key} is the name of an environment variable.
* For system properties, there is no specific format and the value is resolved from a system property that matches the key.
*
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class SystemEnvProperties extends Properties {
/**
* <p>An variation of {@link SystemEnvProperties} that gives unrestricted access to any system variable available at runtime.
* Most of the time you don't want to use this class but favor creating a {@link SystemEnvProperties} instance that
* filters which system variables should be available at runtime.
*/
public static final SystemEnvProperties UNFILTERED = new SystemEnvProperties(Collections.emptySet()) {
@Override
protected boolean isAllowed(String key) {
return true;
}
};
private final Set<String> allowedSystemVariables;
/**
* Creates a new instance where system variables where only specific keys can be resolved from system variables.
*
* @param allowedSystemVariables the keys of system variables that should be available at runtime
*/
public SystemEnvProperties(Set<String> allowedSystemVariables) {
this.allowedSystemVariables = Optional.ofNullable(allowedSystemVariables).orElse(Collections.emptySet());
}
@Override
public String getProperty(String key) {
if (key.startsWith("env.")) {
return System.getenv().get(key.substring(4));
String envKey = key.substring(4);
return isAllowed(envKey) ? System.getenv().get(envKey) : null;
} else {
return System.getProperty(key);
return isAllowed(key) ? System.getProperty(key) : null;
}
}
@ -39,4 +73,7 @@ public class SystemEnvProperties extends Properties {
return value != null ? value : defaultValue;
}
protected boolean isAllowed(String key) {
return allowedSystemVariables.contains(key);
}
}

View File

@ -33,35 +33,35 @@ public class StringPropertyReplacerTest {
@Test
public void testSystemProperties() throws NoSuchAlgorithmException {
System.setProperty("prop1", "val1");
Assert.assertEquals("foo-val1", StringPropertyReplacer.replaceProperties("foo-${prop1}"));
Assert.assertEquals("foo-val1", replaceProperties("foo-${prop1}"));
Assert.assertEquals("foo-def", StringPropertyReplacer.replaceProperties("foo-${prop2:def}"));
Assert.assertEquals("foo-def", replaceProperties("foo-${prop2:def}"));
System.setProperty("prop2", "val2");
Assert.assertEquals("foo-val2", StringPropertyReplacer.replaceProperties("foo-${prop2:def}"));
Assert.assertEquals("foo-val2", replaceProperties("foo-${prop2:def}"));
// It looks for the property "prop3", then fallback to "prop4", then fallback to "prop5" and finally default value.
// This syntax is supported by Quarkus (and underlying Microprofile)
Assert.assertEquals("foo-def", StringPropertyReplacer.replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
Assert.assertEquals("foo-def", replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
System.setProperty("prop5", "val5");
Assert.assertEquals("foo-val5", StringPropertyReplacer.replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
Assert.assertEquals("foo-val5", replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
System.setProperty("prop4", "val4");
Assert.assertEquals("foo-val4", StringPropertyReplacer.replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
Assert.assertEquals("foo-val4", replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
System.setProperty("prop3", "val3");
Assert.assertEquals("foo-val3", StringPropertyReplacer.replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
Assert.assertEquals("foo-val3", replaceProperties("foo-${prop3:${prop4:${prop5:def}}}"));
// It looks for the property "prop6", then fallback to "prop7" then fallback to value "def" .
// This syntax is not supported by Quarkus (microprofile), however Wildfly probably supports this
Assert.assertEquals("foo-def", StringPropertyReplacer.replaceProperties("foo-${prop6,prop7:def}"));
Assert.assertEquals("foo-def", replaceProperties("foo-${prop6,prop7:def}"));
System.setProperty("prop7", "val7");
Assert.assertEquals("foo-val7", StringPropertyReplacer.replaceProperties("foo-${prop6,prop7:def}"));
Assert.assertEquals("foo-val7", replaceProperties("foo-${prop6,prop7:def}"));
System.setProperty("prop6", "val6");
Assert.assertEquals("foo-val6", StringPropertyReplacer.replaceProperties("foo-${prop6,prop7:def}"));
Assert.assertEquals("foo-val6", replaceProperties("foo-${prop6,prop7:def}"));
}
@Test
public void testStackOverflow() {
System.setProperty("prop", "${prop}");
IllegalStateException ise = Assert.assertThrows(IllegalStateException.class, () -> StringPropertyReplacer.replaceProperties("${prop}"));
IllegalStateException ise = Assert.assertThrows(IllegalStateException.class, () -> replaceProperties("${prop}"));
Assert.assertEquals("Infinite recursion happening when replacing properties on '${prop}'", ise.getMessage());
}
@ -72,9 +72,13 @@ public class StringPropertyReplacerTest {
for (String key : env.keySet()) {
String value = env.get(key);
if ( !(value == null || "".equals(value)) ) {
Assert.assertEquals("foo-" + value, StringPropertyReplacer.replaceProperties("foo-${env." + key + "}"));
Assert.assertEquals("foo-" + value, replaceProperties("foo-${env." + key + "}"));
break;
}
}
}
private String replaceProperties(String key) {
return StringPropertyReplacer.replaceProperties(key, SystemEnvProperties.UNFILTERED::getProperty);
}
}

View File

@ -17,8 +17,15 @@
package org.keycloak;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.StringPropertyReplacer.PropertyResolver;
import org.keycloak.common.util.SystemEnvProperties;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -28,6 +35,14 @@ public class Config {
public static void init(ConfigProvider configProvider) {
Config.configProvider = configProvider;
StringPropertyReplacer.setDefaultPropertyResolver(new PropertyResolver() {
SystemEnvProperties systemVariables = new SystemEnvProperties(Config.getAllowedSystemVariables());
@Override
public String resolve(String property) {
return systemVariables.getProperty(property);
}
});
}
public static String getAdminRealm() {
@ -56,6 +71,22 @@ public class Config {
return configProvider.scope(scope);
}
private static Set<String> getAllowedSystemVariables() {
Scope adminScope = configProvider.scope("admin");
if (adminScope == null) {
return Collections.emptySet();
}
String[] allowedSystemVariables = adminScope.getArray("allowed-system-variables");
if (allowedSystemVariables == null) {
return Collections.emptySet();
}
return new HashSet<>(Arrays.asList(allowedSystemVariables));
}
public static interface ConfigProvider {
String getProvider(String spi);

View File

@ -40,7 +40,6 @@ import java.io.OutputStream;
public class JsonSerialization {
public static final ObjectMapper mapper = new ObjectMapper();
public static final ObjectMapper prettyMapper = new ObjectMapper();
public static final ObjectMapper sysPropertiesAwareMapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
static {
mapper.registerModule(new Jdk8Module());
@ -80,7 +79,7 @@ public class JsonSerialization {
}
public static <T> T readValue(InputStream bytes, Class<T> type) throws IOException {
return readValue(bytes, type, false);
return mapper.readValue(bytes, type);
}
public static <T> T readValue(String string, TypeReference<T> type) throws IOException {
@ -91,14 +90,6 @@ public class JsonSerialization {
return mapper.readValue(bytes, type);
}
public static <T> T readValue(InputStream bytes, Class<T> type, boolean replaceSystemProperties) throws IOException {
if (replaceSystemProperties) {
return sysPropertiesAwareMapper.readValue(bytes, type);
} else {
return mapper.readValue(bytes, type);
}
}
/**
* Creates an {@link ObjectNode} based on the given {@code pojo}, copying all its properties to the resulting {@link ObjectNode}.
*

View File

@ -23,7 +23,6 @@ import org.keycloak.common.util.ObjectUtil;
import org.keycloak.representations.ClaimsRepresentation;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation;
import org.keycloak.representations.idm.ClientPolicyConditionRepresentation;
@ -97,30 +96,6 @@ public class JsonParserTest {
Assert.assertNotNull(nested.get("foo"));
}
@Test
public void testParsingSystemProps() throws IOException {
System.setProperty("my.host", "foo");
System.setProperty("con.pool.size", "200");
System.setProperty("allow.any.hostname", "true");
System.setProperty("socket.timeout.millis", "6000");
System.setProperty("connection.timeout.millis", "7000");
System.setProperty("connection.ttl.millis", "500");
InputStream is = getClass().getClassLoader().getResourceAsStream("keycloak.json");
AdapterConfig config = JsonSerialization.readValue(is, AdapterConfig.class, true);
Assert.assertEquals("http://foo:8080/auth", config.getAuthServerUrl());
Assert.assertEquals("external", config.getSslRequired());
Assert.assertEquals("angular-product${non.existing}", config.getResource());
Assert.assertTrue(config.isPublicClient());
Assert.assertTrue(config.isAllowAnyHostname());
Assert.assertEquals(100, config.getCorsMaxAge());
Assert.assertEquals(200, config.getConnectionPoolSize());
Assert.assertEquals(6000L, config.getSocketTimeout());
Assert.assertEquals(7000L, config.getConnectionTimeout());
Assert.assertEquals(500L, config.getConnectionTTL());
}
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
@Test
@ -263,4 +238,4 @@ public class JsonParserTest {
}
}
}
}

View File

@ -0,0 +1,7 @@
= Deprecating using system variables in the realm configuration
To favor a more secure server runtime and avoid to accidentally expose system variables, you are now forced to specify
which system variables you want to expose by using the `spi-admin-allowed-system-variables` configuration option when
starting the server.
In future releases, this capability will be removed in favor of preventing any usage of system variables in the realm configuration.

View File

@ -266,6 +266,20 @@ https-certificate-file
You can achieve most optimizations to startup and runtime behavior by using the `build` command. Also, by using the `keycloak.conf` file as a configuration source, you avoid some steps at startup that would otherwise require command line parameters, such as initializing the CLI itself. As a result, the server starts up even faster.
== Using system variables in the realm configuration
Some of the realm capabilities allow administrators to reference system variables such as environment variables and system properties when configuring
the realm and its components.
By default, {project_name} disallow using system variables but only those explicitly specified through the `spi-admin-allowed-system-variables` configuration
option. This option allows you to specify a comma-separated list of keys that will eventually resolve to values from system variables with the same key.
. Start the server and expose a set of system variables to the server runtime
+
<@kc.start parameters="--spi-admin-allowed-system-variables=FOO,BAR"/>
In future releases, this capability will be removed in favor of preventing any usage of system variables in the realm configuration.
== Underlying concepts
This section gives an overview of the underlying concepts {project_name} uses, especially when it comes to optimizing the startup.

View File

@ -377,7 +377,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
url = addH2NonKeywords(url);
}
Class.forName(driver);
return DriverManager.getConnection(StringPropertyReplacer.replaceProperties(url, System.getProperties()), config.get("user"), config.get("password"));
return DriverManager.getConnection(StringPropertyReplacer.replaceProperties(url, System.getProperties()::getProperty), config.get("user"), config.get("password"));
}
} catch (Exception e) {
throw new RuntimeException("Failed to connect to database", e);

View File

@ -17,6 +17,7 @@
package org.keycloak.saml.common.util;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
import org.keycloak.saml.common.ErrorCodes;
import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory;
@ -210,7 +211,7 @@ public class StaxParserUtil {
final String value = attribute.getValue();
return value == null ? null : trim(StringPropertyReplacer.replaceProperties(value));
return value == null ? null : trim(StringPropertyReplacer.replaceProperties(value, SystemEnvProperties.UNFILTERED::getProperty));
}
/**
@ -250,7 +251,7 @@ public class StaxParserUtil {
*/
public static String getAttributeValueRP(StartElement startElement, HasQName attrName) {
final String value = getAttributeValue(startElement, attrName.getQName());
return value == null ? null : StringPropertyReplacer.replaceProperties(value);
return value == null ? null : StringPropertyReplacer.replaceProperties(value, SystemEnvProperties.UNFILTERED::getProperty);
}
/**
@ -535,7 +536,7 @@ public class StaxParserUtil {
*/
public static String getElementTextRP(XMLEventReader xmlEventReader) throws ParsingException {
try {
return trim(StringPropertyReplacer.replaceProperties(xmlEventReader.getElementText()));
return trim(StringPropertyReplacer.replaceProperties(xmlEventReader.getElementText(), SystemEnvProperties.UNFILTERED::getProperty));
} catch (XMLStreamException e) {
throw logger.parserException(e);
}

View File

@ -26,7 +26,6 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.TokenIdGenerator;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.Time;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.constants.AdapterConstants;
@ -58,7 +57,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
@ -77,8 +75,7 @@ public class ResourceAdminManager {
}
public static String resolveUri(KeycloakSession session, String rootUrl, String uri) {
String absoluteURI = ResolveRelative.resolveRelativeUri(session, rootUrl, uri);
return StringPropertyReplacer.replaceProperties(absoluteURI);
return ResolveRelative.resolveRelativeUri(session, rootUrl, uri);
}
@ -88,10 +85,7 @@ public class ResourceAdminManager {
return null;
}
String absoluteURI = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), mgmtUrl);
// this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine and avoid request to LB in cluster environment
return StringPropertyReplacer.replaceProperties(absoluteURI);
return ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), mgmtUrl);
}
// For non-cluster setup, return just single configured managementUrls
@ -192,10 +186,7 @@ public class ResourceAdminManager {
return null;
}
String absoluteURI = ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), backchannelLogoutUrl);
// this is for resolving URI like "http://${jboss.host.name}:8080/..." in order to send request to same machine
// and avoid request to LB in cluster environment
return StringPropertyReplacer.replaceProperties(absoluteURI);
return ResolveRelative.resolveRelativeUri(session, client.getRootUrl(), backchannelLogoutUrl);
}
protected Response sendBackChannelLogoutRequestToClientUri(ClientModel resource,

View File

@ -264,7 +264,7 @@ public class AccountRestService {
private ConsentRepresentation modelToRepresentation(UserConsentModel model) {
List<ConsentScopeRepresentation> grantedScopes = model.getGrantedClientScopes().stream()
.map(m -> new ConsentScopeRepresentation(m.getId(), m.getConsentScreenText()!= null ? m.getConsentScreenText() : m.getName(), StringPropertyReplacer.replaceProperties(m.getConsentScreenText(), getProperties())))
.map(m -> new ConsentScopeRepresentation(m.getId(), m.getConsentScreenText()!= null ? m.getConsentScreenText() : m.getName(), StringPropertyReplacer.replaceProperties(m.getConsentScreenText(), getProperties()::getProperty)))
.collect(Collectors.toList());
return new ConsentRepresentation(grantedScopes, model.getCreatedDate(), model.getLastUpdatedDate());
}

View File

@ -17,6 +17,7 @@
package org.keycloak.services.util;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.urls.UrlType;
@ -36,14 +37,19 @@ public class ResolveRelative {
}
public static String resolveRelativeUri(String frontendUrl, String adminUrl, String rootUrl, String url) {
String finalUrl;
if (url == null || !url.startsWith("/")) {
return url;
finalUrl = url;
} else if (rootUrl != null && !rootUrl.isEmpty()) {
return resolveRootUrl(frontendUrl, adminUrl, rootUrl) + url;
finalUrl = resolveRootUrl(frontendUrl, adminUrl, rootUrl) + url;
} else {
return UriBuilder.fromUri(frontendUrl).replacePath(url).build().toString();
finalUrl = UriBuilder.fromUri(frontendUrl).replacePath(url).build().toString();
}
return StringPropertyReplacer.replaceProperties(finalUrl);
}
public static String resolveRootUrl(KeycloakSession session, String rootUrl) {
String frontendUrl = session.getContext().getUri(UrlType.FRONTEND).getBaseUri().toString();
String adminUrl = session.getContext().getUri(UrlType.ADMIN).getBaseUri().toString();

View File

@ -352,7 +352,7 @@ public class DefaultThemeManager implements ThemeManager {
*/
private void substituteProperties(final Properties properties) {
for (final String propertyName : properties.stringPropertyNames()) {
properties.setProperty(propertyName, StringPropertyReplacer.replaceProperties(properties.getProperty(propertyName), new SystemEnvProperties()));
properties.setProperty(propertyName, StringPropertyReplacer.replaceProperties(properties.getProperty(propertyName), SystemEnvProperties.UNFILTERED::getProperty));
}
}
}

View File

@ -15,27 +15,24 @@
* limitations under the License.
*/
package org.keycloak.services.util;
package org.keycloak.utils;
import java.util.Set;
import com.fasterxml.jackson.databind.JsonNode;
import org.keycloak.Config;
import org.keycloak.common.util.StringPropertyReplacer;
import java.util.Properties;
import java.util.Set;
import org.keycloak.common.util.SystemEnvProperties;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JsonConfigProvider implements Config.ConfigProvider {
private Properties properties;
private JsonNode config;
public JsonConfigProvider(JsonNode config, Properties properties) {
public JsonConfigProvider(JsonNode config) {
this.config = config;
this.properties = properties;
}
@Override
@ -70,7 +67,7 @@ public class JsonConfigProvider implements Config.ConfigProvider {
}
private String replaceProperties(String value) {
return StringPropertyReplacer.replaceProperties(value, properties);
return StringPropertyReplacer.replaceProperties(value, SystemEnvProperties.UNFILTERED::getProperty);
}
public class JsonScope implements Config.Scope {

View File

@ -18,15 +18,13 @@
package org.keycloak.utils;
import org.junit.Assert;
import org.keycloak.services.util.JsonConfigProvider;
import org.keycloak.services.util.JsonConfigProvider.JsonScope;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.keycloak.utils.JsonConfigProvider.JsonScope;
public class ScopeUtil {
@ -34,7 +32,7 @@ public class ScopeUtil {
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode config = mapper.readTree(json(properties));
return new JsonConfigProvider(config, new Properties()).new JsonScope(config);
return new JsonConfigProvider(config).new JsonScope(config);
} catch (IOException e) {
Assert.fail("Could not parse json");
}

View File

@ -42,6 +42,7 @@ import org.jboss.logging.Logger;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.error.KeycloakErrorHandler;
import org.keycloak.testsuite.ProfileAssume;
@ -436,7 +437,7 @@ public class AuthServerTestEnricher {
log.infof("Running SQL script created by liquibase during manual migration flow", sqlScriptPath);
String prefix = "keycloak.connectionsJpa.";
String jdbcDriver = System.getProperty(prefix + "driver");
String dbUrl = StringPropertyReplacer.replaceProperties(System.getProperty(prefix + "url"));
String dbUrl = StringPropertyReplacer.replaceProperties(System.getProperty(prefix + "url"), SystemEnvProperties.UNFILTERED::getProperty);
String dbUser = System.getProperty(prefix + "user");
String dbPassword = System.getProperty(prefix + "password");

View File

@ -29,6 +29,7 @@ import org.jboss.arquillian.container.test.impl.client.deployment.AnnotationDepl
import org.jboss.arquillian.test.spi.TestClass;
import org.jboss.logging.Logger;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.getAppServerQualifiers;
@ -100,7 +101,7 @@ public class DeploymentTargetModifier extends AnnotationDeploymentScenarioGenera
String newAppServerQualifier = ContainerConstants.APP_SERVER_PREFIX + AppServerTestEnricher.CURRENT_APP_SERVER + "-" + suffix;
updateServerQualifier(deployment, testClass, newAppServerQualifier);
} else {
String newServerQualifier = StringPropertyReplacer.replaceProperties(containerQualifier);
String newServerQualifier = StringPropertyReplacer.replaceProperties(containerQualifier, SystemEnvProperties.UNFILTERED::getProperty);
if (!newServerQualifier.equals(containerQualifier)) {
updateServerQualifier(deployment, testClass, newServerQualifier);
}

View File

@ -39,6 +39,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@ -97,8 +98,8 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
InputStream is = KcSamlIdPInitiatedSsoTest.class.getResourceAsStream(fileName);
try {
String template = StreamUtil.readString(is, Charset.defaultCharset());
String realmString = StringPropertyReplacer.replaceProperties(template, properties);
return IOUtil.loadRealm(new ByteArrayInputStream(realmString.getBytes("UTF-8")));
String realmString = StringPropertyReplacer.replaceProperties(template, properties::getProperty);
return IOUtil.loadRealm(new ByteArrayInputStream(realmString.getBytes(StandardCharsets.UTF_8)));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
@ -139,7 +140,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
p.put("url.realm.provider", urlRealmProvider);
p.put("url.realm.consumer", urlRealmConsumer);
p.put("url.realm.consumer-2", urlRealmConsumer2);
testRealms.add(loadFromClasspath("kc3731-provider-realm.json", p));
testRealms.add(loadFromClasspath("kc3731-broker-realm.json", p));
}
@ -399,7 +400,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
assertThat(fed.getUserId(), is(PROVIDER_REALM_USER_NAME));
assertThat(fed.getUserName(), is(PROVIDER_REALM_USER_NAME));
}
@Test
public void testProviderTransientIdpInitiatedLogin() throws Exception {
IdentityProviderResource idp = adminClient.realm(REALM_CONS_NAME).identityProviders().get("saml-leaf");
@ -426,7 +427,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
nameId.setFormat(URI.create(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get()));
nameId.setValue("subjectId1" );
resp.getAssertions().get(0).getAssertion().getSubject().getSubType().addBaseID(nameId);
Set<StatementAbstractType> statements = resp.getAssertions().get(0).getAssertion().getStatements();
AttributeStatementType attributeType = (AttributeStatementType) statements.stream()
@ -448,7 +449,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
// Login in provider realm
.login().sso(true).build()
.processSamlResponse(Binding.POST)
.transformObject(ob -> {
assertThat(ob, Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
@ -460,7 +461,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
nameId.setFormat(URI.create(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get()));
nameId.setValue("subjectId2" );
resp.getAssertions().get(0).getAssertion().getSubject().getSubType().addBaseID(nameId);
Set<StatementAbstractType> statements = resp.getAssertions().get(0).getAssertion().getStatements();
AttributeStatementType attributeType = (AttributeStatementType) statements.stream()
@ -487,7 +488,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
ResponseType resp = (ResponseType) samlResponse.getSamlObject();
assertThat(resp.getDestination(), is(urlRealmConsumer + "/app/auth2/saml"));
assertAudience(resp, urlRealmConsumer + "/app/auth2");
UsersResource users = adminClient.realm(REALM_CONS_NAME).users();
List<UserRepresentation> userList= users.search(CONSUMER_CHOSEN_USERNAME);
assertEquals(1, userList.size());
@ -495,7 +496,7 @@ public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
FederatedIdentityRepresentation fed = users.get(id).getFederatedIdentity().get(0);
assertThat(fed.getUserId(), is(PROVIDER_REALM_USER_NAME));
assertThat(fed.getUserName(), is(PROVIDER_REALM_USER_NAME));
//check that no user with sent subject-id was sent
userList = users.search("subjectId1");
assertTrue(userList.isEmpty());

View File

@ -21,9 +21,9 @@ import org.keycloak.Config.Scope;
import org.keycloak.Config.SystemPropertiesScope;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.common.util.SystemEnvProperties;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@ -34,8 +34,6 @@ import java.util.stream.Collectors;
*/
public class Config implements ConfigProvider {
private final Properties systemProperties = new SystemEnvProperties();
private final Map<String, String> defaultProperties = new ConcurrentHashMap<>();
private final ThreadLocal<Map<String, String>> properties = new ThreadLocal<Map<String, String>>() {
@Override
@ -157,7 +155,7 @@ public class Config implements ConfigProvider {
}
private String replaceProperties(String value) {
return StringPropertyReplacer.replaceProperties(value, systemProperties);
return StringPropertyReplacer.replaceProperties(value, SystemEnvProperties.UNFILTERED::getProperty);
}
@Override

View File

@ -23,13 +23,11 @@ import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.Properties;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.util.SystemEnvProperties;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.util.JsonConfigProvider;
import org.keycloak.util.JsonSerialization;
import org.keycloak.utils.JsonConfigProvider;
public class JsonConfigProviderFactory implements ConfigProviderFactory {
@ -66,11 +64,6 @@ public class JsonConfigProviderFactory implements ConfigProviderFactory {
}
protected Optional<Config.ConfigProvider> createJsonProvider(JsonNode node) {
return Optional.ofNullable(node).map(n -> new JsonConfigProvider(n, getProperties()));
return Optional.ofNullable(node).map(n -> new JsonConfigProvider(n));
}
protected Properties getProperties() {
return new SystemEnvProperties();
}
}