iso-date validator for the user-profile

Adds a new validator in order to be able to validate user-model fields which should be modified/supplied by a datepicker.

Closes #11757

Signed-off-by: Thore <thore@kruess.xyz>
This commit is contained in:
Thore 2024-04-12 00:39:23 +02:00 committed by Pedro Igor
parent d4b7e1a7d9
commit 4b194d00be
5 changed files with 97 additions and 10 deletions

View file

@ -273,6 +273,10 @@ The list below provides a list of all the built-in validators:
|Check if the value has a valid format based on the realm and/or user locale.
| None
|iso-date
|Check if the value has a valid format based on ISO 8601. This validator can be used with inputs using the html5-date input type.
| None
|person-name-prohibited-characters
| Check if the value is a valid person name as an additional barrier for attacks such as script injection. The validation is based on a default RegEx pattern that blocks characters not common in person names.
|

View file

@ -22,6 +22,7 @@ package org.keycloak.validate;
import org.keycloak.validate.validators.DoubleValidator;
import org.keycloak.validate.validators.EmailValidator;
import org.keycloak.validate.validators.IntegerValidator;
import org.keycloak.validate.validators.IsoDateValidator;
import org.keycloak.validate.validators.LengthValidator;
import org.keycloak.validate.validators.LocalDateValidator;
import org.keycloak.validate.validators.NotBlankValidator;
@ -72,6 +73,10 @@ public class BuiltinValidators {
return LocalDateValidator.INSTANCE;
}
public static IsoDateValidator isoDateValidator() {
return IsoDateValidator.INSTANCE;
}
public static OptionsValidator optionsValidator() {
return OptionsValidator.INSTANCE;
}

View file

@ -0,0 +1,57 @@
package org.keycloak.validate.validators;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.validate.AbstractStringValidator;
import org.keycloak.validate.ValidationContext;
import org.keycloak.validate.ValidationError;
import org.keycloak.validate.ValidatorConfig;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.List;
/**
* A date validator that only takes into account the format associated with the current locale.
*/
public class IsoDateValidator extends AbstractStringValidator implements ConfiguredProvider {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
public static final String MESSAGE_INVALID_DATE = "error-invalid-date";
public static final IsoDateValidator INSTANCE = new IsoDateValidator();
public static final String ID = "iso-date";
@Override
public String getId() {
return ID;
}
@Override
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
try {
FORMATTER.parse(value);
} catch (DateTimeParseException e) {
context.addError(new ValidationError(getId(), inputHint, MESSAGE_INVALID_DATE, value));
}
}
@Override
public String getHelpText() {
return "Validates date in rfc3339/iso8601 format, as provided by the html5-date input.";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
@Override
protected boolean isIgnoreEmptyValuesConfigured(ValidatorConfig config) {
return true;
}
}

View file

@ -8,3 +8,4 @@ org.keycloak.validate.validators.DoubleValidator
org.keycloak.validate.validators.IntegerValidator
org.keycloak.validate.validators.LocalDateValidator
org.keycloak.validate.validators.OptionsValidator
org.keycloak.validate.validators.IsoDateValidator

View file

@ -19,12 +19,6 @@
package org.keycloak.testsuite.validation;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.Locale;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@ -32,8 +26,14 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.runonserver.RunOnServer;
import org.keycloak.validate.ValidationContext;
import org.keycloak.validate.BuiltinValidators;
import org.keycloak.validate.ValidationContext;
import java.util.Collections;
import java.util.Locale;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -45,17 +45,23 @@ public class ValidatorTest extends AbstractTestRealmKeycloakTest {
}
@Test
public void testDateValidator() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) ValidatorTest::testDateValidator);
public void testLocalDateValidator() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) ValidatorTest::testLocalDateValidator);
}
private static void testDateValidator(KeycloakSession session) {
@Test
public void testIsoDateValidator() {
getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) ValidatorTest::testIsoDateValidator);
}
private static void testLocalDateValidator(KeycloakSession session) {
assertTrue(BuiltinValidators.dateValidator().validate(null, new ValidationContext(session)).isValid());
assertTrue(BuiltinValidators.dateValidator().validate("", new ValidationContext(session)).isValid());
// defaults to Locale.ENGLISH as per default locale selector
assertFalse(BuiltinValidators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid());
assertTrue(BuiltinValidators.dateValidator().validate("12/13/21", new ValidationContext(session)).isValid());
assertTrue(BuiltinValidators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid());
RealmModel realm = session.getContext().getRealm();
@ -76,4 +82,18 @@ public class ValidatorTest extends AbstractTestRealmKeycloakTest {
assertFalse(BuiltinValidators.dateValidator().validate("13/12/2021", context).isValid());
}
private static void testIsoDateValidator(KeycloakSession session) {
assertTrue(BuiltinValidators.isoDateValidator().validate(null, new ValidationContext(session)).isValid());
assertTrue(BuiltinValidators.isoDateValidator().validate("", new ValidationContext(session)).isValid());
assertTrue(BuiltinValidators.isoDateValidator().validate("2021-12-13", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("13/12/2021", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("13/12/21", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("12/13/21", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("13.12.21", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("13.12.2021", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("2021-13-12", new ValidationContext(session)).isValid());
assertFalse(BuiltinValidators.isoDateValidator().validate("21-13-12", new ValidationContext(session)).isValid());
}
}