fix: Optionally hide rendered environment variables (#798)

* new OriginType.ENV_VARIABLE
* deserialization of unknown originType ordinal, but this doesn't really
  help for compatibility if serialized with new version and deserialized
  with old version
This commit is contained in:
Patrik Nordwall 2023-10-17 08:59:00 +02:00 committed by GitHub
parent 5d6a46631c
commit 3a4ebbfd02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 122 additions and 19 deletions

View File

@ -95,7 +95,11 @@ lazy val configLib = Project("config", file("config"))
"CONFIG_FORCE_a__c" -> "3", "CONFIG_FORCE_a__c" -> "3",
"CONFIG_FORCE_a___c" -> "4", "CONFIG_FORCE_a___c" -> "4",
"CONFIG_FORCE_akka_version" -> "foo", "CONFIG_FORCE_akka_version" -> "foo",
"CONFIG_FORCE_akka_event__handler__dispatcher_max__pool__size" -> "10") "CONFIG_FORCE_akka_event__handler__dispatcher_max__pool__size" -> "10",
"SECRET_A" -> "A", // ConfigTest.renderShowEnvVariableValues
"SECRET_B" -> "B", // ConfigTest.renderShowEnvVariableValues
"SECRET_C" -> "C" // ConfigTest.renderShowEnvVariableValues
)
OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl") OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl")
publish := sys.error("use publishSigned instead of plain publish") publish := sys.error("use publishSigned instead of plain publish")

View File

@ -21,13 +21,15 @@ public final class ConfigRenderOptions {
private final boolean comments; private final boolean comments;
private final boolean formatted; private final boolean formatted;
private final boolean json; private final boolean json;
private final boolean showEnvVariableValues;
private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted, private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted,
boolean json) { boolean json, boolean showEnvVariableValues) {
this.originComments = originComments; this.originComments = originComments;
this.comments = comments; this.comments = comments;
this.formatted = formatted; this.formatted = formatted;
this.json = json; this.json = json;
this.showEnvVariableValues = showEnvVariableValues;
} }
/** /**
@ -38,7 +40,7 @@ public final class ConfigRenderOptions {
* @return the default render options * @return the default render options
*/ */
public static ConfigRenderOptions defaults() { public static ConfigRenderOptions defaults() {
return new ConfigRenderOptions(true, true, true, true); return new ConfigRenderOptions(true, true, true, true, true);
} }
/** /**
@ -48,7 +50,7 @@ public final class ConfigRenderOptions {
* @return the concise render options * @return the concise render options
*/ */
public static ConfigRenderOptions concise() { public static ConfigRenderOptions concise() {
return new ConfigRenderOptions(false, false, false, true); return new ConfigRenderOptions(false, false, false, true, true);
} }
/** /**
@ -64,7 +66,7 @@ public final class ConfigRenderOptions {
if (value == comments) if (value == comments)
return this; return this;
else else
return new ConfigRenderOptions(originComments, value, formatted, json); return new ConfigRenderOptions(originComments, value, formatted, json, showEnvVariableValues);
} }
/** /**
@ -97,7 +99,7 @@ public final class ConfigRenderOptions {
if (value == originComments) if (value == originComments)
return this; return this;
else else
return new ConfigRenderOptions(value, comments, formatted, json); return new ConfigRenderOptions(value, comments, formatted, json, showEnvVariableValues);
} }
/** /**
@ -122,7 +124,7 @@ public final class ConfigRenderOptions {
if (value == formatted) if (value == formatted)
return this; return this;
else else
return new ConfigRenderOptions(originComments, comments, value, json); return new ConfigRenderOptions(originComments, comments, value, json, showEnvVariableValues);
} }
/** /**
@ -150,7 +152,32 @@ public final class ConfigRenderOptions {
if (value == json) if (value == json)
return this; return this;
else else
return new ConfigRenderOptions(originComments, comments, formatted, value); return new ConfigRenderOptions(originComments, comments, formatted, value, showEnvVariableValues);
}
/**
* Returns options with showEnvVariableValues toggled. This controls if values set from
* environment variables are included in the rendered string.
*
* @param value
* true to include environment variable values in the render
* @return options with requested setting for environment variables
*/
public ConfigRenderOptions setShowEnvVariableValues(boolean value) {
if (value == showEnvVariableValues)
return this;
else
return new ConfigRenderOptions(originComments, comments, formatted, json, value);
}
/**
* Returns whether the options enable rendering of environment variable values. This method is mostly used
* by the config lib internally, not by applications.
*
* @return true if environment variable values should be rendered
*/
public boolean getShowEnvVariableValues() {
return showEnvVariableValues;
} }
/** /**
@ -174,6 +201,8 @@ public final class ConfigRenderOptions {
sb.append("formatted,"); sb.append("formatted,");
if (json) if (json)
sb.append("json,"); sb.append("json,");
if (showEnvVariableValues)
sb.append("showEnvVariableValues,");
if (sb.charAt(sb.length() - 1) == ',') if (sb.charAt(sb.length() - 1) == ',')
sb.setLength(sb.length() - 1); sb.setLength(sb.length() - 1);
sb.append(")"); sb.append(")");

View File

@ -357,8 +357,20 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
} }
protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
Object u = unwrapped(); if (hideEnvVariableValue(options)) {
sb.append(u.toString()); sb.append("<env variable>");
} else {
Object u = unwrapped();
sb.append(u.toString());
}
}
protected boolean hideEnvVariableValue(ConfigRenderOptions options) {
return !options.getShowEnvVariableValues() && origin.originType() == OriginType.ENV_VARIABLE;
}
protected void appendHiddenEnvVariableValue(StringBuilder sb) {
sb.append("\"<env variable>\"");
} }
@Override @Override

View File

@ -342,7 +342,7 @@ public class ConfigImpl {
} }
private static AbstractConfigObject loadEnvVariables() { private static AbstractConfigObject loadEnvVariables() {
return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv()); return PropertiesParser.fromStringMap(newEnvVariable("env variables"), System.getenv());
} }
private static class EnvVariablesHolder { private static class EnvVariablesHolder {
@ -544,4 +544,8 @@ public class ConfigImpl {
public static ConfigOrigin newURLOrigin(URL url) { public static ConfigOrigin newURLOrigin(URL url) {
return SimpleConfigOrigin.newURL(url); return SimpleConfigOrigin.newURL(url);
} }
public static ConfigOrigin newEnvVariable(String description) {
return SimpleConfigOrigin.newEnvVariable(description);
}
} }

View File

@ -80,11 +80,15 @@ abstract class ConfigString extends AbstractConfigValue implements Serializable
@Override @Override
protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
String rendered; if (hideEnvVariableValue(options)) {
if (options.getJson()) appendHiddenEnvVariableValue(sb);
rendered = ConfigImplUtil.renderJsonString(value); } else {
else String rendered;
rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value); if (options.getJson())
sb.append(rendered); rendered = ConfigImplUtil.renderJsonString(value);
else
rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value);
sb.append(rendered);
}
} }
} }

View File

@ -5,5 +5,6 @@ enum OriginType {
GENERIC, GENERIC,
FILE, FILE,
URL, URL,
RESOURCE RESOURCE,
ENV_VARIABLE
} }

View File

@ -77,6 +77,10 @@ final class SimpleConfigOrigin implements ConfigOrigin {
return newResource(resource, null); return newResource(resource, null);
} }
static SimpleConfigOrigin newEnvVariable(String description) {
return new SimpleConfigOrigin(description, -1, -1, OriginType.ENV_VARIABLE, null, null, null);
}
@Override @Override
public SimpleConfigOrigin withLineNumber(int lineNumber) { public SimpleConfigOrigin withLineNumber(int lineNumber) {
if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) {
@ -139,6 +143,10 @@ final class SimpleConfigOrigin implements ConfigOrigin {
} }
} }
OriginType originType() {
return originType;
}
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (other instanceof SimpleConfigOrigin) { if (other instanceof SimpleConfigOrigin) {
@ -484,7 +492,11 @@ final class SimpleConfigOrigin implements ConfigOrigin {
Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE); Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE);
if (originTypeOrdinal == null) if (originTypeOrdinal == null)
throw new IOException("Missing ORIGIN_TYPE field"); throw new IOException("Missing ORIGIN_TYPE field");
OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()]; OriginType originType;
if (originTypeOrdinal.byteValue() < OriginType.values().length)
originType = OriginType.values()[originTypeOrdinal.byteValue()];
else
originType = OriginType.GENERIC; // ENV_VARIABLE was added in a later version
String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL); String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL);
String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE); String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -0,0 +1,4 @@
secret = a
secret = ${?SECRET_A}
secrets = ["b", "c"]
secrets = [${?SECRET_B}, ${?SECRET_C}]

View File

@ -1,3 +1,5 @@
package foo;
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
@ -6,11 +8,13 @@ object RenderExample extends App {
val originComments = args.contains("--origin-comments") val originComments = args.contains("--origin-comments")
val comments = args.contains("--comments") val comments = args.contains("--comments")
val hocon = args.contains("--hocon") val hocon = args.contains("--hocon")
val hideEnvVariableValues = args.contains("--hide-env-variable-values")
val options = ConfigRenderOptions.defaults() val options = ConfigRenderOptions.defaults()
.setFormatted(formatted) .setFormatted(formatted)
.setOriginComments(originComments) .setOriginComments(originComments)
.setComments(comments) .setComments(comments)
.setJson(!hocon) .setJson(!hocon)
.setShowEnvVariableValues(!hideEnvVariableValues)
def render(what: String) { def render(what: String) {
val conf = ConfigFactory.defaultOverrides() val conf = ConfigFactory.defaultOverrides()

View File

@ -1207,6 +1207,35 @@ class ConfigTest extends TestUtils {
} }
} }
@Test
def renderShowEnvVariableValues(): Unit = {
val config = ConfigFactory.load("env-variables")
assertEquals("A", config.getString("secret"))
assertEquals("B", config.getStringList("secrets").get(0))
assertEquals("C", config.getStringList("secrets").get(1))
val hideRenderOpt = ConfigRenderOptions.defaults().setShowEnvVariableValues(false)
val rendered1 = config.root().render(hideRenderOpt)
assertTrue(rendered1.contains(""""secret" : "<env variable>""""))
assertTrue(rendered1.contains(
"""| "secrets" : [
| # env variables
| "<env variable>",
| # env variables
| "<env variable>"
| ]""".stripMargin))
val showRenderOpt = ConfigRenderOptions.defaults()
val rendered2 = config.root().render(showRenderOpt)
assertTrue(rendered2.contains(""""secret" : "A""""))
assertTrue(rendered2.contains(
"""| "secrets" : [
| # env variables
| "B",
| # env variables
| "C"
| ]""".stripMargin))
}
@Test @Test
def serializeRoundTrip() { def serializeRoundTrip() {
for (i <- 1 to 10) { for (i <- 1 to 10) {