fix: simplify / refine validation methods (#32487)
closes: #32455 Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
parent
0fcbec8daa
commit
081a3852c2
8 changed files with 63 additions and 36 deletions
|
@ -35,7 +35,7 @@ public final class BootstrapAdminPropertyMappers {
|
||||||
return new PropertyMapper[]{
|
return new PropertyMapper[]{
|
||||||
fromOption(BootstrapAdminOptions.USERNAME)
|
fromOption(BootstrapAdminOptions.USERNAME)
|
||||||
.paramLabel("username")
|
.paramLabel("username")
|
||||||
.appendValidateEnabled(BootstrapAdminPropertyMappers::isPasswordSet, PASSWORD_SET)
|
.addValidateEnabled(BootstrapAdminPropertyMappers::isPasswordSet, PASSWORD_SET)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(BootstrapAdminOptions.PASSWORD)
|
fromOption(BootstrapAdminOptions.PASSWORD)
|
||||||
.paramLabel("password")
|
.paramLabel("password")
|
||||||
|
@ -47,7 +47,7 @@ public final class BootstrapAdminPropertyMappers {
|
||||||
.build(),*/
|
.build(),*/
|
||||||
fromOption(BootstrapAdminOptions.CLIENT_ID)
|
fromOption(BootstrapAdminOptions.CLIENT_ID)
|
||||||
.paramLabel("client id")
|
.paramLabel("client id")
|
||||||
.appendValidateEnabled(BootstrapAdminPropertyMappers::isClientSecretSet, CLIENT_SECRET_SET)
|
.addValidateEnabled(BootstrapAdminPropertyMappers::isClientSecretSet, CLIENT_SECRET_SET)
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(BootstrapAdminOptions.CLIENT_SECRET)
|
fromOption(BootstrapAdminOptions.CLIENT_SECRET)
|
||||||
.paramLabel("client secret")
|
.paramLabel("client secret")
|
||||||
|
|
|
@ -69,7 +69,7 @@ public final class ExportPropertyMappers {
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ExportOptions.USERS)
|
fromOption(ExportOptions.USERS)
|
||||||
.to("kc.spi-export-dir-users-export-strategy")
|
.to("kc.spi-export-dir-users-export-strategy")
|
||||||
.validator(ExportPropertyMappers::validateUsersUsage)
|
.addValidator(ExportPropertyMappers::validateUsersUsage)
|
||||||
.paramLabel("strategy")
|
.paramLabel("strategy")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ExportOptions.USERS_PER_FILE)
|
fromOption(ExportOptions.USERS_PER_FILE)
|
||||||
|
@ -81,8 +81,6 @@ public final class ExportPropertyMappers {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateUsersUsage(PropertyMapper<?> mapper, ConfigValue value) {
|
private static void validateUsersUsage(PropertyMapper<?> mapper, ConfigValue value) {
|
||||||
mapper.validateExpectedValues(value, mapper::validateSingleValue);
|
|
||||||
|
|
||||||
if (!isBlank(ExportOptions.FILE) && isBlank(ExportOptions.DIR)) {
|
if (!isBlank(ExportOptions.FILE) && isBlank(ExportOptions.DIR)) {
|
||||||
var sameFileIsSpecified = UsersExportStrategy.SAME_FILE.toString().toLowerCase().equals(value.getValue());
|
var sameFileIsSpecified = UsersExportStrategy.SAME_FILE.toString().toLowerCase().equals(value.getValue());
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ public final class FeaturePropertyMappers {
|
||||||
return new PropertyMapper[] {
|
return new PropertyMapper[] {
|
||||||
fromOption(FeatureOptions.FEATURES)
|
fromOption(FeatureOptions.FEATURES)
|
||||||
.paramLabel("feature")
|
.paramLabel("feature")
|
||||||
.validator((mapper, value) -> mapper.validateExpectedValues(value,
|
.validator(FeaturePropertyMappers::validateEnabledFeature)
|
||||||
(c, v) -> validateEnabledFeature(v)))
|
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(FeatureOptions.FEATURES_DISABLED)
|
fromOption(FeatureOptions.FEATURES_DISABLED)
|
||||||
.paramLabel("feature")
|
.paramLabel("feature")
|
||||||
|
|
|
@ -94,8 +94,7 @@ public final class LoggingPropertyMappers {
|
||||||
fromOption(LoggingOptions.LOG_LEVEL)
|
fromOption(LoggingOptions.LOG_LEVEL)
|
||||||
.to("quarkus.log.level")
|
.to("quarkus.log.level")
|
||||||
.transformer(LoggingPropertyMappers::resolveLogLevel)
|
.transformer(LoggingPropertyMappers::resolveLogLevel)
|
||||||
.validator((mapper, value) -> mapper.validateExpectedValues(value,
|
.validator(LoggingPropertyMappers::validateLogLevel)
|
||||||
(c, v) -> validateLogLevel(v)))
|
|
||||||
.paramLabel("category:level")
|
.paramLabel("category:level")
|
||||||
.build(),
|
.build(),
|
||||||
// Syslog
|
// Syslog
|
||||||
|
|
|
@ -76,25 +76,25 @@ public class ManagementPropertyMappers {
|
||||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE)
|
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE)
|
||||||
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey())
|
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey())
|
||||||
.to("quarkus.management.ssl.certificate.files")
|
.to("quarkus.management.ssl.certificate.files")
|
||||||
.validator((mapper, value) -> validateTlsProperties())
|
.validator(value -> validateTlsProperties())
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE)
|
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE)
|
||||||
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE.getKey())
|
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE.getKey())
|
||||||
.to("quarkus.management.ssl.certificate.key-files")
|
.to("quarkus.management.ssl.certificate.key-files")
|
||||||
.validator((mapper, value) -> validateTlsProperties())
|
.validator(value -> validateTlsProperties())
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE)
|
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE)
|
||||||
.mapFrom(HttpOptions.HTTPS_KEY_STORE_FILE.getKey())
|
.mapFrom(HttpOptions.HTTPS_KEY_STORE_FILE.getKey())
|
||||||
.to("quarkus.management.ssl.certificate.key-store-file")
|
.to("quarkus.management.ssl.certificate.key-store-file")
|
||||||
.validator((mapper, value) -> validateTlsProperties())
|
.validator(value -> validateTlsProperties())
|
||||||
.paramLabel("file")
|
.paramLabel("file")
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_PASSWORD)
|
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_PASSWORD)
|
||||||
.mapFrom(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey())
|
.mapFrom(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey())
|
||||||
.to("quarkus.management.ssl.certificate.key-store-password")
|
.to("quarkus.management.ssl.certificate.key-store-password")
|
||||||
.validator((mapper, value) -> validateTlsProperties())
|
.validator(value -> validateTlsProperties())
|
||||||
.paramLabel("password")
|
.paramLabel("password")
|
||||||
.isMasked(true)
|
.isMasked(true)
|
||||||
.build(),
|
.build(),
|
||||||
|
|
|
@ -24,10 +24,13 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.toCliForm
|
||||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.toEnvVarFormat;
|
import static org.keycloak.quarkus.runtime.configuration.Configuration.toEnvVarFormat;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
|
@ -297,7 +300,7 @@ public class PropertyMapper<T> {
|
||||||
private BooleanSupplier isEnabled = () -> true;
|
private BooleanSupplier isEnabled = () -> true;
|
||||||
private String enabledWhen = "";
|
private String enabledWhen = "";
|
||||||
private String paramLabel;
|
private String paramLabel;
|
||||||
private BiConsumer<PropertyMapper<T>, ConfigValue> validator = (mapper, value) -> mapper.validateExpectedValues(value, mapper::validateSingleValue);
|
private BiConsumer<PropertyMapper<T>, ConfigValue> validator = (mapper, value) -> mapper.validateValues(value, mapper::validateExpectedValues);
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
public Builder(Option<T> option) {
|
public Builder(Option<T> option) {
|
||||||
|
@ -349,27 +352,41 @@ public class PropertyMapper<T> {
|
||||||
/**
|
/**
|
||||||
* Set the validator, overwriting the current one.
|
* Set the validator, overwriting the current one.
|
||||||
*/
|
*/
|
||||||
public Builder<T> validator(BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
public Builder<T> validator(Consumer<String> validator) {
|
||||||
this.validator = validator;
|
this.validator = (mapper, value) -> mapper.validateValues(value,
|
||||||
|
(c, v) -> validator.accept(v));
|
||||||
|
if (!Objects.equals(this.description, this.option.getDescription())) {
|
||||||
|
throw new AssertionError("Overwriting the validator will cause the description modification from addValidateEnabled to be incorrect.");
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder<T> appendValidator(BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
public Builder<T> addValidator(BiConsumer<PropertyMapper<T>, ConfigValue> validator) {
|
||||||
var current = this.validator;
|
var current = this.validator;
|
||||||
this.validator = (mapper, value) -> {
|
this.validator = (mapper, value) -> {
|
||||||
validator.accept(mapper, value);
|
Stream.of(current, validator).map(v -> {
|
||||||
current.accept(mapper, value);
|
try {
|
||||||
|
v.accept(mapper, value);
|
||||||
|
return Optional.<PropertyException>empty();
|
||||||
|
} catch (PropertyException e) {
|
||||||
|
return Optional.of(e);
|
||||||
|
}
|
||||||
|
}).flatMap(Optional::stream)
|
||||||
|
.reduce((e1, e2) -> new PropertyException(String.format("%s.\n%s", e1.getMessage(), e2.getMessage())))
|
||||||
|
.ifPresent(e -> {
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to {@link #enabledWhen}, but uses the condition as a validator that is appended to the current one. This allows the option
|
* Similar to {@link #enabledWhen}, but uses the condition as a validator that is added to the current one. This allows the option
|
||||||
* to appear in help.
|
* to appear in help.
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Builder<T> appendValidateEnabled(BooleanSupplier isEnabled, String enabledWhen) {
|
public Builder<T> addValidateEnabled(BooleanSupplier isEnabled, String enabledWhen) {
|
||||||
this.appendValidator((mapper, value) -> {
|
this.addValidator((mapper, value) -> {
|
||||||
if (!isEnabled.getAsBoolean()) {
|
if (!isEnabled.getAsBoolean()) {
|
||||||
throw new PropertyException(mapper.getOption().getKey() + " available only when " + enabledWhen);
|
throw new PropertyException(mapper.getOption().getKey() + " available only when " + enabledWhen);
|
||||||
}
|
}
|
||||||
|
@ -396,18 +413,34 @@ public class PropertyMapper<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateExpectedValues(ConfigValue configValue, BiConsumer<ConfigValue, String> singleValidator) {
|
public void validateValues(ConfigValue configValue, BiConsumer<ConfigValue, String> singleValidator) {
|
||||||
String value = configValue.getValue();
|
String value = configValue.getValue();
|
||||||
|
|
||||||
boolean multiValued = getOption().getType() == java.util.List.class;
|
boolean multiValued = getOption().getType() == java.util.List.class;
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
String[] values = multiValued ? value.split(",") : new String[] { value };
|
String[] values = multiValued ? value.split(",") : new String[] { value };
|
||||||
for (String v : values) {
|
for (String v : values) {
|
||||||
if (multiValued && !v.trim().equals(v)) {
|
if (multiValued && !v.trim().equals(v)) {
|
||||||
throw new PropertyException("Invalid value for multivalued option " + getOptionAndSourceMessage(configValue)
|
if (!result.isEmpty()) {
|
||||||
+ ": list value '" + v + "' should not have leading nor trailing whitespace");
|
result.append(".\n");
|
||||||
}
|
}
|
||||||
|
result.append("Invalid value for multivalued option " + getOptionAndSourceMessage(configValue)
|
||||||
|
+ ": list value '" + v + "' should not have leading nor trailing whitespace");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
singleValidator.accept(configValue, v);
|
singleValidator.accept(configValue, v);
|
||||||
|
} catch (PropertyException e) {
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
result.append(".\n");
|
||||||
|
}
|
||||||
|
result.append(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
throw new PropertyException(result.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,7 +452,7 @@ public class PropertyMapper<T> {
|
||||||
return Optional.ofNullable(configValue.getConfigSourceName()).filter(name -> name.contains(KcEnvConfigSource.NAME)).isPresent();
|
return Optional.ofNullable(configValue.getConfigSourceName()).filter(name -> name.contains(KcEnvConfigSource.NAME)).isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateSingleValue(ConfigValue configValue, String v) {
|
void validateExpectedValues(ConfigValue configValue, String v) {
|
||||||
List<String> expectedValues = getExpectedValues();
|
List<String> expectedValues = getExpectedValues();
|
||||||
if (!expectedValues.isEmpty() && !expectedValues.contains(v) && getOption().isStrictExpectedValues()) {
|
if (!expectedValues.isEmpty() && !expectedValues.contains(v) && getOption().isStrictExpectedValues()) {
|
||||||
throw new PropertyException(
|
throw new PropertyException(
|
||||||
|
|
|
@ -38,9 +38,8 @@ final class ProxyPropertyMappers {
|
||||||
.build(),
|
.build(),
|
||||||
fromOption(ProxyOptions.PROXY_TRUSTED_ADDRESSES)
|
fromOption(ProxyOptions.PROXY_TRUSTED_ADDRESSES)
|
||||||
.to("quarkus.http.proxy.trusted-proxies")
|
.to("quarkus.http.proxy.trusted-proxies")
|
||||||
.validator((mapper, value) -> mapper.validateExpectedValues(value,
|
.validator(ProxyPropertyMappers::validateAddress)
|
||||||
(c, v) -> validateAddress(v)))
|
.addValidateEnabled(() -> !Configuration.isBlank(ProxyOptions.PROXY_HEADERS), "proxy-headers is set")
|
||||||
.appendValidateEnabled(() -> !Configuration.isBlank(ProxyOptions.PROXY_HEADERS), "proxy-headers is set")
|
|
||||||
.paramLabel("trusted proxies")
|
.paramLabel("trusted proxies")
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.quarkus.runtime.configuration.mappers;
|
package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||||
|
|
||||||
import io.smallrye.config.ConfigValue;
|
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.quarkus.runtime.Environment;
|
import org.keycloak.quarkus.runtime.Environment;
|
||||||
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||||
|
@ -97,23 +96,23 @@ public class TracingPropertyMappers {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateEndpoint(PropertyMapper<String> mapper, ConfigValue value) {
|
private static void validateEndpoint(String value) {
|
||||||
if (value == null || StringUtil.isBlank(value.getValue())) {
|
if (StringUtil.isBlank(value)) {
|
||||||
throw new PropertyException("URL specified in 'tracing-endpoint' option must not be empty.");
|
throw new PropertyException("URL specified in 'tracing-endpoint' option must not be empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidUrl(value.getValue())) {
|
if (!isValidUrl(value)) {
|
||||||
throw new PropertyException("URL specified in 'tracing-endpoint' option is invalid.");
|
throw new PropertyException("URL specified in 'tracing-endpoint' option is invalid.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateRatio(PropertyMapper<Double> mapper, ConfigValue value) {
|
private static void validateRatio(String value) {
|
||||||
if (value == null || StringUtil.isBlank(value.getValue())) {
|
if (StringUtil.isBlank(value)) {
|
||||||
throw new PropertyException("Ratio in 'tracing-sampler-ratio' option must not be empty.");
|
throw new PropertyException("Ratio in 'tracing-sampler-ratio' option must not be empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var ratio = Double.parseDouble(value.getValue());
|
var ratio = Double.parseDouble(value);
|
||||||
if (ratio <= 0.0 || ratio > 1.0) {
|
if (ratio <= 0.0 || ratio > 1.0) {
|
||||||
throw new NumberFormatException();
|
throw new NumberFormatException();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue