Move some UserProfile and Validation classes into keycloak-server-spi

closes #24387
This commit is contained in:
mposolda 2023-10-30 14:46:10 +01:00 committed by Pedro Igor
parent 75440abb5f
commit 6f992915d7
27 changed files with 396 additions and 351 deletions

View file

@ -1,240 +0,0 @@
/*
* Copyright 2021 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.validate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.validate.validators.LocalDateValidator;
import org.keycloak.validate.validators.EmailValidator;
import org.keycloak.validate.validators.IntegerValidator;
import org.keycloak.validate.validators.LengthValidator;
import org.keycloak.validate.validators.NotBlankValidator;
import org.keycloak.validate.validators.NotEmptyValidator;
import org.keycloak.validate.validators.OptionsValidator;
import org.keycloak.validate.validators.DoubleValidator;
import org.keycloak.validate.validators.PatternValidator;
import org.keycloak.validate.validators.UriValidator;
import org.keycloak.validate.validators.ValidatorConfigValidator;
/**
* Facade for Validation functions with support for {@link Validator} implementation lookup by id.
*/
public class Validators {
/**
* Holds a mapping of internal {@link SimpleValidator} to allow look-up via provider id.
*/
private static final Map<String, SimpleValidator> INTERNAL_VALIDATORS;
static {
List<SimpleValidator> list = Arrays.asList(
LengthValidator.INSTANCE,
NotEmptyValidator.INSTANCE,
UriValidator.INSTANCE,
EmailValidator.INSTANCE,
NotBlankValidator.INSTANCE,
PatternValidator.INSTANCE,
DoubleValidator.INSTANCE,
IntegerValidator.INSTANCE,
ValidatorConfigValidator.INSTANCE,
OptionsValidator.INSTANCE
);
INTERNAL_VALIDATORS = list.stream().collect(Collectors.toMap(SimpleValidator::getId, v -> v));
}
/**
* Holds the {@link KeycloakSession}.
*/
private final KeycloakSession session;
/**
* Creates a new {@link Validators} instance with the given {@link KeycloakSession}.
*
* @param session
*/
public Validators(KeycloakSession session) {
this.session = session;
}
/**
* Look-up for a built-in or registered {@link Validator} with the given provider {@code id}.
*
* @param id
* @return
* @see #validator(KeycloakSession, String)
*/
public Validator validator(String id) {
return validator(session, id);
}
/**
* Look-up for a built-in or registered {@link ValidatorFactory} with the given provider {@code id}.
*
* @param id
* @return
* @see #validatorFactory(KeycloakSession, String)
*/
public ValidatorFactory validatorFactory(String id) {
return validatorFactory(session, id);
}
/**
* Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}.
*
* @param id
* @param config
* @return
* @see #validateConfig(KeycloakSession, String, ValidatorConfig)
*/
public ValidationResult validateConfig(String id, ValidatorConfig config) {
return validateConfig(session, id, config);
}
/* static import friendly accessor methods for built-in validators */
public static Validator getInternalValidatorById(String id) {
return INTERNAL_VALIDATORS.get(id);
}
public static ValidatorFactory getInternalValidatorFactoryById(String id) {
return INTERNAL_VALIDATORS.get(id);
}
public static Map<String, Validator> getInternalValidators() {
return Collections.unmodifiableMap(INTERNAL_VALIDATORS);
}
public static NotBlankValidator notBlankValidator() {
return NotBlankValidator.INSTANCE;
}
public static NotEmptyValidator notEmptyValidator() {
return NotEmptyValidator.INSTANCE;
}
public static LengthValidator lengthValidator() {
return LengthValidator.INSTANCE;
}
public static UriValidator uriValidator() {
return UriValidator.INSTANCE;
}
public static EmailValidator emailValidator() {
return EmailValidator.INSTANCE;
}
public static PatternValidator patternValidator() {
return PatternValidator.INSTANCE;
}
public static DoubleValidator doubleValidator() {
return DoubleValidator.INSTANCE;
}
public static IntegerValidator integerValidator() {
return IntegerValidator.INSTANCE;
}
public static LocalDateValidator dateValidator() {
return LocalDateValidator.INSTANCE;
}
public static OptionsValidator optionsValidator() {
return OptionsValidator.INSTANCE;
}
public static ValidatorConfigValidator validatorConfigValidator() {
return ValidatorConfigValidator.INSTANCE;
}
/**
* Look-up up for a built-in or registered {@link Validator} with the given validatorId.
*
* @param session the {@link KeycloakSession}
* @param id the id of the validator
* @return the {@link Validator} or {@literal null}
*/
public static Validator validator(KeycloakSession session, String id) {
// Fast-path for internal Validators
Validator validator = getInternalValidatorById(id);
if (validator != null) {
return validator;
}
if (session == null) {
return null;
}
// Lookup validator in registry
return session.getProvider(Validator.class, id);
}
/**
* Look-up for a built-in or registered {@link ValidatorFactory} with the given validatorId.
* <p>
* This is intended for users who want to dynamically create new {@link Validator} instances, validate
* {@link ValidatorConfig} configurations or create default configurations for a {@link Validator}.
*
* @param session the {@link KeycloakSession}
* @param id the id of the validator
* @return the {@link Validator} or {@literal null}
*/
public static ValidatorFactory validatorFactory(KeycloakSession session, String id) {
// Fast-path for internal Validators
ValidatorFactory factory = getInternalValidatorFactoryById(id);
if (factory != null) {
return factory;
}
if (session == null) {
return null;
}
// Lookup factory in registry
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
return (ValidatorFactory) sessionFactory.getProviderFactory(Validator.class, id);
}
/**
* Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}.
*
* @param session
* @param id of the validator
* @param config to be validated
* @return
*/
public static ValidationResult validateConfig(KeycloakSession session, String id, ValidatorConfig config) {
ValidatorFactory validatorFactory = validatorFactory(session, id);
if (validatorFactory != null) {
return validatorFactory.validateConfig(session, config);
}
// We could not find a ValidationFactory to validate that config, so we assume the config is valid.
return ValidationResult.OK;
}
}

View file

@ -57,7 +57,7 @@ public class ValidatorConfigValidator implements SimpleValidator {
public static final ValidatorConfigValidator INSTANCE = new ValidatorConfigValidator();
private ValidatorConfigValidator() {
public ValidatorConfigValidator() {
}
@Override

View file

@ -7,4 +7,5 @@ org.keycloak.validate.validators.PatternValidator
org.keycloak.validate.validators.DoubleValidator
org.keycloak.validate.validators.IntegerValidator
org.keycloak.validate.validators.LocalDateValidator
org.keycloak.validate.validators.OptionsValidator
org.keycloak.validate.validators.OptionsValidator
org.keycloak.validate.validators.ValidatorConfigValidator

View file

@ -1,18 +1,20 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
* Copyright 2023 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
* 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
* 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.
*
* 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.validate;
@ -40,17 +42,6 @@ public interface Validator extends Provider {
return validate(input, "input", new ValidationContext(), ValidatorConfig.EMPTY);
}
/**
* Validates the given {@code input} with an additional {@code config}.
*
* @param input the value to validate
* @param config parameterization for the current validation
* @return the validation context with the outcome of the validation
*/
default ValidationContext validate(Object input, ValidatorConfig config) {
return validate(input, "input", new ValidationContext(), config);
}
/**
* Validates the given {@code input}.
*

View file

@ -0,0 +1,131 @@
/*
* Copyright 2023 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.validate;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* Facade for Validation functions with support for {@link Validator} implementation lookup by id.
*/
public class Validators {
/**
* Holds the {@link KeycloakSession}.
*/
private final KeycloakSession session;
/**
* Creates a new {@link Validators} instance with the given {@link KeycloakSession}.
*
* @param session
*/
public Validators(KeycloakSession session) {
this.session = session;
}
/**
* Look-up for a built-in or registered {@link Validator} with the given provider {@code id}.
*
* @param id
* @return
* @see #validator(KeycloakSession, String)
*/
public Validator validator(String id) {
return validator(session, id);
}
/**
* Look-up for a built-in or registered {@link ValidatorFactory} with the given provider {@code id}.
*
* @param id
* @return
* @see #validatorFactory(KeycloakSession, String)
*/
public ValidatorFactory validatorFactory(String id) {
return validatorFactory(session, id);
}
/**
* Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}.
*
* @param id
* @param config
* @return
* @see #validateConfig(KeycloakSession, String, ValidatorConfig)
*/
public ValidationResult validateConfig(String id, ValidatorConfig config) {
return validateConfig(session, id, config);
}
/**
* Look-up up for a built-in or registered {@link Validator} with the given validatorId.
*
* @param session the {@link KeycloakSession}
* @param id the id of the validator
* @return the {@link Validator} or {@literal null}
*/
public static Validator validator(KeycloakSession session, String id) {
if (session == null) {
throw new IllegalArgumentException("KeycloakSession must be not null");
}
// Lookup validator in registry
return session.getProvider(Validator.class, id);
}
/**
* Look-up for a built-in or registered {@link ValidatorFactory} with the given validatorId.
* <p>
* This is intended for users who want to dynamically create new {@link Validator} instances, validate
* {@link ValidatorConfig} configurations or create default configurations for a {@link Validator}.
*
* @param session the {@link KeycloakSession}
* @param id the id of the validator
* @return the {@link Validator} or {@literal null}
*/
public static ValidatorFactory validatorFactory(KeycloakSession session, String id) {
if (session == null) {
throw new IllegalArgumentException("KeycloakSession must be not null");
}
// Lookup factory in registry
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
return (ValidatorFactory) sessionFactory.getProviderFactory(Validator.class, id);
}
/**
* Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}.
*
* @param session
* @param id of the validator
* @param config to be validated
* @return
*/
public static ValidationResult validateConfig(KeycloakSession session, String id, ValidatorConfig config) {
ValidatorFactory validatorFactory = validatorFactory(session, id);
if (validatorFactory != null) {
return validatorFactory.validateConfig(session, config);
}
// We could not find a ValidationFactory to validate that config, so we assume the config is valid.
return ValidationResult.OK;
}
}

View file

@ -17,23 +17,20 @@
package org.keycloak.userprofile.validator;
import static org.keycloak.common.util.CollectionUtil.collectionEquals;
import static org.keycloak.validate.Validators.notBlankValidator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.userprofile.AttributeContext;
import org.keycloak.userprofile.AttributeValidatorMetadata;
import org.keycloak.userprofile.UserProfileAttributeValidationContext;
import org.keycloak.validate.SimpleValidator;
import org.keycloak.validate.ValidationContext;
import org.keycloak.validate.ValidationError;
import org.keycloak.validate.ValidatorConfig;
import org.keycloak.validate.Validators;
import org.keycloak.validate.validators.NotBlankValidator;
/**
* A validator that fails when the attribute is marked as read only and its value has changed.
@ -65,7 +62,7 @@ public class ImmutableAttributeValidator implements SimpleValidator {
List<String> values = (List<String>) input;
if (!collectionEquals(currentValue, values) && isReadOnly(attributeContext)) {
if (currentValue.isEmpty() && !notBlankValidator().validate(values).isValid()) {
if (currentValue.isEmpty() && !NotBlankValidator.INSTANCE.validate(values).isValid()) {
return context;
}

View file

@ -1,4 +1,23 @@
package org.keycloak.validate;
/*
* Copyright 2023 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.testsuite.validation;
import static org.keycloak.validate.ValidatorConfig.configFromMap;
@ -11,6 +30,17 @@ import java.util.regex.Pattern;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
import org.keycloak.validate.AbstractSimpleValidator;
import org.keycloak.validate.ValidationContext;
import org.keycloak.validate.ValidationError;
import org.keycloak.validate.ValidationResult;
import org.keycloak.validate.Validator;
import org.keycloak.validate.ValidatorConfig;
import org.keycloak.validate.validators.DoubleValidator;
import org.keycloak.validate.validators.EmailValidator;
import org.keycloak.validate.validators.IntegerValidator;
@ -21,14 +51,18 @@ import org.keycloak.validate.validators.UriValidator;
import com.google.common.collect.ImmutableMap;
public class BuiltinValidatorsTest {
public class BuiltinValidatorsTest extends AbstractKeycloakTest {
private static final ValidatorConfig valConfigIgnoreEmptyValues = ValidatorConfig.builder().config(AbstractSimpleValidator.IGNORE_EMPTY_VALUE, true).build();
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}
@Test
public void testLengthValidator() {
Validator validator = Validators.lengthValidator();
Validator validator = SimpleValidators.lengthValidator();
// null and empty values handling
Assert.assertFalse(validator.validate(null, "name", configFromMap(ImmutableMap.of(LengthValidator.KEY_MIN, 1))).isValid());
@ -84,12 +118,13 @@ public class BuiltinValidatorsTest {
}
@Test
public void testLengthValidator_ConfigValidation() {
@ModelTest
public void testLengthValidator_ConfigValidation(KeycloakSession session) {
// invalid min and max config values
ValidatorConfig config = new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, new Object(), LengthValidator.KEY_MAX, "invalid"));
ValidationResult result = Validators.validatorConfigValidator().validate(config, LengthValidator.ID).toResult();
ValidationResult result = SimpleValidators.validatorConfigValidator().validate(config, LengthValidator.ID, new ValidationContext(session)).toResult();
Assert.assertFalse(result.isValid());
ValidationError[] errors = result.getErrors().toArray(new ValidationError[0]);
@ -105,25 +140,25 @@ public class BuiltinValidatorsTest {
Assert.assertEquals(LengthValidator.KEY_MAX, error1.getInputHint());
// empty config
result = Validators.validatorConfigValidator().validate(null, LengthValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(null, LengthValidator.ID, new ValidationContext(session)).toResult();
Assert.assertEquals(2, result.getErrors().size());
result = Validators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, LengthValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, LengthValidator.ID, new ValidationContext(session)).toResult();
Assert.assertEquals(2, result.getErrors().size());
// correct config
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10")), LengthValidator.ID).toResult().isValid());
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MAX, "10")), LengthValidator.ID).toResult().isValid());
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "10")), LengthValidator.ID).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MAX, "10")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "10")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid());
// max is smaller than min
Assert.assertFalse(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "9")), LengthValidator.ID).toResult().isValid());
Assert.assertFalse(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "9")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid());
}
@Test
public void testEmailValidator() {
// this also validates StringFormatValidatorBase for simple values
Validator validator = Validators.emailValidator();
Validator validator = SimpleValidators.emailValidator();
Assert.assertFalse(validator.validate(null, "email").isValid());
Assert.assertFalse(validator.validate("", "email").isValid());
@ -156,7 +191,7 @@ public class BuiltinValidatorsTest {
@Test
public void testAbstractSimpleValidatorSupportForCollections() {
Validator validator = Validators.emailValidator();
Validator validator = SimpleValidators.emailValidator();
List<String> valuesCollection = new ArrayList<>();
@ -180,7 +215,7 @@ public class BuiltinValidatorsTest {
@Test
public void testNotBlankValidator() {
Validator validator = Validators.notBlankValidator();
Validator validator = SimpleValidators.notBlankValidator();
// simple String value
Assert.assertTrue(validator.validate("tester", "username").isValid());
@ -203,7 +238,7 @@ public class BuiltinValidatorsTest {
@Test
public void testNotEmptyValidator() {
Validator validator = Validators.notEmptyValidator();
Validator validator = SimpleValidators.notEmptyValidator();
Assert.assertTrue(validator.validate("tester", "username").isValid());
Assert.assertTrue(validator.validate(" ", "username").isValid());
@ -221,7 +256,7 @@ public class BuiltinValidatorsTest {
@Test
public void testDoubleValidator() {
Validator validator = Validators.doubleValidator();
Validator validator = SimpleValidators.doubleValidator();
// null value and empty String
Assert.assertFalse(validator.validate(null, "null").isValid());
@ -285,12 +320,13 @@ public class BuiltinValidatorsTest {
}
@Test
public void testDoubleValidator_ConfigValidation() {
@ModelTest
public void testDoubleValidator_ConfigValidation(KeycloakSession session) {
// invalid min and max config values
ValidatorConfig config = new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, new Object(), DoubleValidator.KEY_MAX, "invalid"));
ValidationResult result = Validators.validatorConfigValidator().validate(config, DoubleValidator.ID).toResult();
ValidationResult result = SimpleValidators.validatorConfigValidator().validate(config, DoubleValidator.ID, new ValidationContext(session)).toResult();
Assert.assertFalse(result.isValid());
ValidationError[] errors = result.getErrors().toArray(new ValidationError[0]);
@ -306,23 +342,23 @@ public class BuiltinValidatorsTest {
Assert.assertEquals(DoubleValidator.KEY_MAX, error1.getInputHint());
// empty config
result = Validators.validatorConfigValidator().validate(null, DoubleValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(null, DoubleValidator.ID, new ValidationContext(session)).toResult();
Assert.assertEquals(0, result.getErrors().size());
result = Validators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, DoubleValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, DoubleValidator.ID, new ValidationContext(session)).toResult();
Assert.assertEquals(0, result.getErrors().size());
// correct config
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1")), DoubleValidator.ID).toResult().isValid());
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID).toResult().isValid());
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "11")), DoubleValidator.ID).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "11")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid());
// max is smaller than min
Assert.assertFalse(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID).toResult().isValid());
Assert.assertFalse(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid());
}
@Test
public void testIntegerValidator() {
Validator validator = Validators.integerValidator();
Validator validator = SimpleValidators.integerValidator();
// null value and empty String
Assert.assertFalse(validator.validate(null, "null").isValid());
@ -387,12 +423,13 @@ public class BuiltinValidatorsTest {
}
@Test
public void testIntegerValidator_ConfigValidation() {
@ModelTest
public void testIntegerValidator_ConfigValidation(KeycloakSession session) {
// invalid min and max config values
ValidatorConfig config = new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, new Object(), IntegerValidator.KEY_MAX, "invalid"));
ValidationResult result = Validators.validatorConfigValidator().validate(config, IntegerValidator.ID).toResult();
ValidationResult result = SimpleValidators.validatorConfigValidator().validate(config, IntegerValidator.ID, new ValidationContext(session)).toResult();
Assert.assertFalse(result.isValid());
ValidationError[] errors = result.getErrors().toArray(new ValidationError[0]);
@ -408,24 +445,24 @@ public class BuiltinValidatorsTest {
Assert.assertEquals(IntegerValidator.KEY_MAX, error1.getInputHint());
// empty config
result = Validators.validatorConfigValidator().validate(null, IntegerValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(null, IntegerValidator.ID, new ValidationContext(session)).toResult();
Assert.assertEquals(0, result.getErrors().size());
result = Validators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, IntegerValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, IntegerValidator.ID, new ValidationContext(session)).toResult();
Assert.assertEquals(0, result.getErrors().size());
// correct config
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10")), IntegerValidator.ID).toResult().isValid());
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID).toResult().isValid());
Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "11")), IntegerValidator.ID).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid());
Assert.assertTrue(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "11")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid());
// max is smaller than min
Assert.assertFalse(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID).toResult().isValid());
Assert.assertFalse(SimpleValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid());
}
@Test
public void testPatternValidator() {
Validator validator = Validators.patternValidator();
Validator validator = SimpleValidators.patternValidator();
// Pattern object in the configuration
ValidatorConfig config = configFromMap(Collections.singletonMap(PatternValidator.CFG_PATTERN, Pattern.compile("^start-.*-end$")));
@ -456,7 +493,7 @@ public class BuiltinValidatorsTest {
@Test
public void testUriValidator() throws Exception {
Validator validator = Validators.uriValidator();
Validator validator = SimpleValidators.uriValidator();
Assert.assertTrue(validator.validate(null, "baseUrl").isValid());
Assert.assertTrue(validator.validate("", "baseUrl").isValid());
@ -472,14 +509,14 @@ public class BuiltinValidatorsTest {
Assert.assertFalse(validator.validate("https://localhost:3000/#someFragment", "baseUrl", config).isValid());
// it is also possible to call dedicated validation methods on a built-in validator
Assert.assertTrue(Validators.uriValidator().validateUri(new URI("https://customurl"), Collections.singleton("https"), true, true));
Assert.assertTrue(SimpleValidators.uriValidator().validateUri(new URI("https://customurl"), Collections.singleton("https"), true, true));
Assert.assertFalse(Validators.uriValidator().validateUri(new URI("http://customurl"), Collections.singleton("https"), true, true));
Assert.assertFalse(SimpleValidators.uriValidator().validateUri(new URI("http://customurl"), Collections.singleton("https"), true, true));
}
@Test
public void testOptionsValidator(){
Validator validator = Validators.optionsValidator();
Validator validator = SimpleValidators.optionsValidator();
// options not configured - always invalid
Assert.assertFalse(validator.validate(null, "test", ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, null).build()).isValid());
@ -517,16 +554,17 @@ public class BuiltinValidatorsTest {
}
@Test
public void testOptionsValidator_Config_Validation() {
@ModelTest
public void testOptionsValidator_Config_Validation(KeycloakSession session) {
ValidationResult result = Validators.validatorConfigValidator().validate(ValidatorConfig.builder().build(), OptionsValidator.ID).toResult();
ValidationResult result = SimpleValidators.validatorConfigValidator().validate(ValidatorConfig.builder().build(), OptionsValidator.ID, new ValidationContext(session)).toResult();
Assert.assertFalse(result.isValid());
// invalid type of the config value
result = Validators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, "a").build(), OptionsValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, "a").build(), OptionsValidator.ID, new ValidationContext(session)).toResult();
Assert.assertFalse(result.isValid());
result = Validators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, Arrays.asList("opt1")).build(), OptionsValidator.ID).toResult();
result = SimpleValidators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, Arrays.asList("opt1")).build(), OptionsValidator.ID, new ValidationContext(session)).toResult();
Assert.assertTrue(result.isValid());
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2023 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.testsuite.validation;
import org.keycloak.validate.validators.DoubleValidator;
import org.keycloak.validate.validators.EmailValidator;
import org.keycloak.validate.validators.IntegerValidator;
import org.keycloak.validate.validators.LengthValidator;
import org.keycloak.validate.validators.LocalDateValidator;
import org.keycloak.validate.validators.NotBlankValidator;
import org.keycloak.validate.validators.NotEmptyValidator;
import org.keycloak.validate.validators.OptionsValidator;
import org.keycloak.validate.validators.PatternValidator;
import org.keycloak.validate.validators.UriValidator;
import org.keycloak.validate.validators.ValidatorConfigValidator;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SimpleValidators {
public static NotBlankValidator notBlankValidator() {
return NotBlankValidator.INSTANCE;
}
public static NotEmptyValidator notEmptyValidator() {
return NotEmptyValidator.INSTANCE;
}
public static LengthValidator lengthValidator() {
return LengthValidator.INSTANCE;
}
public static UriValidator uriValidator() {
return UriValidator.INSTANCE;
}
public static EmailValidator emailValidator() {
return EmailValidator.INSTANCE;
}
public static PatternValidator patternValidator() {
return PatternValidator.INSTANCE;
}
public static DoubleValidator doubleValidator() {
return DoubleValidator.INSTANCE;
}
public static IntegerValidator integerValidator() {
return IntegerValidator.INSTANCE;
}
public static LocalDateValidator dateValidator() {
return LocalDateValidator.INSTANCE;
}
public static OptionsValidator optionsValidator() {
return OptionsValidator.INSTANCE;
}
public static ValidatorConfigValidator validatorConfigValidator() {
return ValidatorConfigValidator.INSTANCE;
}
}

View file

@ -51,21 +51,21 @@ public class ValidatorTest extends AbstractTestRealmKeycloakTest {
}
private static void testDateValidator(KeycloakSession session) {
assertTrue(Validators.dateValidator().validate(null, new ValidationContext(session)).isValid());
assertTrue(Validators.dateValidator().validate("", new ValidationContext(session)).isValid());
assertTrue(SimpleValidators.dateValidator().validate(null, new ValidationContext(session)).isValid());
assertTrue(SimpleValidators.dateValidator().validate("", new ValidationContext(session)).isValid());
// defaults to Locale.ENGLISH as per default locale selector
assertFalse(Validators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid());
assertFalse(Validators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid());
assertTrue(Validators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid());
assertFalse(SimpleValidators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid());
assertFalse(SimpleValidators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid());
assertTrue(SimpleValidators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid());
RealmModel realm = session.getContext().getRealm();
realm.setInternationalizationEnabled(true);
realm.setDefaultLocale(Locale.FRANCE.getLanguage());
assertTrue(Validators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid());
assertTrue(Validators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid());
assertFalse(Validators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid());
assertTrue(SimpleValidators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid());
assertTrue(SimpleValidators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid());
assertFalse(SimpleValidators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid());
UserModel alice = session.users().getUserByUsername(realm, "alice");
@ -75,6 +75,6 @@ public class ValidatorTest extends AbstractTestRealmKeycloakTest {
context.getAttributes().put(UserModel.class.getName(), alice);
assertFalse(Validators.dateValidator().validate("13/12/2021", context).isValid());
assertFalse(SimpleValidators.dateValidator().validate("13/12/2021", context).isValid());
}
}

View file

@ -1,4 +1,23 @@
package org.keycloak.validate;
/*
* Copyright 2023 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.testsuite.validation;
import static org.keycloak.validate.ValidatorConfig.configFromMap;
@ -14,28 +33,41 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.ModelTest;
import org.keycloak.validate.SimpleValidator;
import org.keycloak.validate.ValidationContext;
import org.keycloak.validate.ValidationError;
import org.keycloak.validate.ValidationResult;
import org.keycloak.validate.Validator;
import org.keycloak.validate.ValidatorConfig;
import org.keycloak.validate.Validators;
import org.keycloak.validate.validators.EmailValidator;
import org.keycloak.validate.validators.LengthValidator;
import org.keycloak.validate.validators.NotBlankValidator;
import org.keycloak.validate.validators.ValidatorConfigValidator;
public class ValidatorTest {
public class ValidatorsTest extends AbstractKeycloakTest {
KeycloakSession session = null;
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}
@Test
public void simpleValidation() {
Validator validator = Validators.notEmptyValidator();
Validator validator = SimpleValidators.notEmptyValidator();
Assert.assertTrue(validator.validate("a").isValid());
Assert.assertFalse(validator.validate("").isValid());
}
@Test
public void simpleValidationWithContext() {
@ModelTest
public void simpleValidationWithContext(KeycloakSession session) {
Validator validator = Validators.lengthValidator();
Validator validator = SimpleValidators.lengthValidator();
ValidationContext context = new ValidationContext(session);
validator.validate("a", "username", context);
@ -45,17 +77,19 @@ public class ValidatorTest {
}
@Test
public void simpleValidationFluent() {
@ModelTest
public void simpleValidationFluent(KeycloakSession session) {
ValidationContext context = new ValidationContext(session);
ValidationResult result = Validators.lengthValidator().validate("a", "username", context).toResult();
ValidationResult result = SimpleValidators.lengthValidator().validate("a", "username", context).toResult();
Assert.assertTrue(result.isValid());
}
@Test
public void simpleValidationLookup() {
@ModelTest
public void simpleValidationLookup(KeycloakSession session) {
// later: session.validators().validator(LengthValidator.ID);
Validator validator = Validators.validator(session, LengthValidator.ID);
@ -68,7 +102,8 @@ public class ValidatorTest {
}
@Test
public void simpleValidationError() {
@ModelTest
public void simpleValidationError(KeycloakSession session) {
Validator validator = LengthValidator.INSTANCE;
@ -104,20 +139,21 @@ public class ValidatorTest {
public void acceptOnError() {
AtomicBoolean bool1 = new AtomicBoolean();
Validators.notEmptyValidator().validate("a").toResult().ifNotValidAccept(r -> bool1.set(true));
SimpleValidators.notEmptyValidator().validate("a").toResult().ifNotValidAccept(r -> bool1.set(true));
Assert.assertFalse(bool1.get());
AtomicBoolean bool2 = new AtomicBoolean();
Validators.notEmptyValidator().validate("").toResult().ifNotValidAccept(r -> bool2.set(true));
SimpleValidators.notEmptyValidator().validate("").toResult().ifNotValidAccept(r -> bool2.set(true));
Assert.assertTrue(bool2.get());
}
@Test
public void forEachError() {
@ModelTest
public void forEachError(KeycloakSession session) {
List<String> errors = new ArrayList<>();
MockAddress faultyAddress = new MockAddress("", "Saint-Maur-des-Fossés", null, "Germany");
MockAddressValidator.INSTANCE.validate(faultyAddress, "address").toResult().forEachError(e -> {
MockAddressValidator.INSTANCE.validate(faultyAddress, "address", new ValidationContext(session)).toResult().forEachError(e -> {
errors.add(e.getMessage());
});
@ -125,7 +161,8 @@ public class ValidatorTest {
}
@Test
public void formatError() {
@ModelTest
public void formatError(KeycloakSession session) {
Map<String, String> miniResourceBundle = new HashMap<>();
miniResourceBundle.put("error-invalid-blank", "{0} is blank: <{1}>");
@ -133,7 +170,7 @@ public class ValidatorTest {
List<String> errors = new ArrayList<>();
MockAddress faultyAddress = new MockAddress("", "Saint-Maur-des-Fossés", null, "Germany");
MockAddressValidator.INSTANCE.validate(faultyAddress, "address").toResult().forEachError(e -> {
MockAddressValidator.INSTANCE.validate(faultyAddress, "address", new ValidationContext(session)).toResult().forEachError(e -> {
errors.add(e.formatMessage((message, args) -> MessageFormat.format(miniResourceBundle.getOrDefault(message, message), args)));
});
@ -141,15 +178,16 @@ public class ValidatorTest {
}
@Test
public void multipleValidations() {
@ModelTest
public void multipleValidations(KeycloakSession session) {
ValidationContext context = new ValidationContext(session);
String input = "aaa";
String inputHint = "username";
Validators.lengthValidator().validate(input, inputHint, context);
Validators.notEmptyValidator().validate(input, inputHint, context);
SimpleValidators.lengthValidator().validate(input, inputHint, context);
SimpleValidators.notEmptyValidator().validate(input, inputHint, context);
ValidationResult result = context.toResult();
@ -157,15 +195,16 @@ public class ValidatorTest {
}
@Test
public void multipleValidationsError() {
@ModelTest
public void multipleValidationsError(KeycloakSession session) {
ValidationContext context = new ValidationContext(session);
String input = " ";
String inputHint = "username";
Validators.lengthValidator().validate(input, inputHint, context, configFromMap(Collections.singletonMap(LengthValidator.KEY_MIN, 1)));
Validators.notBlankValidator().validate(input, inputHint, context);
SimpleValidators.lengthValidator().validate(input, inputHint, context, configFromMap(Collections.singletonMap(LengthValidator.KEY_MIN, 1)));
SimpleValidators.notBlankValidator().validate(input, inputHint, context);
ValidationResult result = context.toResult();
@ -184,7 +223,8 @@ public class ValidatorTest {
}
@Test
public void validateValidatorConfigSimple() {
@ModelTest
public void validateValidatorConfigSimple(KeycloakSession session) {
SimpleValidator validator = LengthValidator.INSTANCE;
@ -197,8 +237,9 @@ public class ValidatorTest {
}
@Test
public void validateEmailValidator() {
SimpleValidator validator = Validators.emailValidator();
@ModelTest
public void validateEmailValidator(KeycloakSession session) {
SimpleValidator validator = SimpleValidators.emailValidator();
Assert.assertTrue(validator.validateConfig(session, null).isValid());
Assert.assertTrue(validator.validateConfig(session, ValidatorConfig.EMPTY).isValid());
@ -215,7 +256,8 @@ public class ValidatorTest {
}
@Test
public void validateValidatorConfigMultipleOptions() {
@ModelTest
public void validateValidatorConfigMultipleOptions(KeycloakSession session) {
SimpleValidator validator = LengthValidator.INSTANCE;
@ -229,7 +271,8 @@ public class ValidatorTest {
}
@Test
public void validateValidatorConfigMultipleOptionsInvalidValues() {
@ModelTest
public void validateValidatorConfigMultipleOptionsInvalidValues(KeycloakSession session) {
SimpleValidator validator = LengthValidator.INSTANCE;
@ -253,7 +296,8 @@ public class ValidatorTest {
}
@Test
public void validateValidatorConfigViaValidatorFactory() {
@ModelTest
public void validateValidatorConfigViaValidatorFactory(KeycloakSession session) {
Map<String, Object> config = new HashMap<>();
config.put("min", "a");
@ -275,15 +319,16 @@ public class ValidatorTest {
}
@Test
public void nestedValidation() {
@ModelTest
public void nestedValidation(KeycloakSession session) {
Assert.assertTrue(MockAddressValidator.INSTANCE.validate(
new MockAddress("4848 Arcu St.", "Saint-Maur-des-Fossés", "02206", "Germany")
, "address").isValid());
, "address", new ValidationContext(session)).isValid());
ValidationResult result = MockAddressValidator.INSTANCE.validate(
new MockAddress("", "Saint-Maur-des-Fossés", null, "Germany")
, "address").toResult();
, "address", new ValidationContext(session)).toResult();
Assert.assertFalse(result.isValid());
Assert.assertEquals(2, result.getErrors().size());