KEYCLOAK-7724 User Profile default validations
This commit is contained in:
parent
4dacbb9e0b
commit
32f2f095fe
20 changed files with 545 additions and 49 deletions
|
@ -41,6 +41,8 @@ public abstract class AbstractNumberValidator extends AbstractSimpleValidator im
|
||||||
|
|
||||||
public static final String MESSAGE_INVALID_NUMBER = "error-invalid-number";
|
public static final String MESSAGE_INVALID_NUMBER = "error-invalid-number";
|
||||||
public static final String MESSAGE_NUMBER_OUT_OF_RANGE = "error-number-out-of-range";
|
public static final String MESSAGE_NUMBER_OUT_OF_RANGE = "error-number-out-of-range";
|
||||||
|
public static final String MESSAGE_NUMBER_OUT_OF_RANGE_TOO_SMALL = "error-number-out-of-range-too-small";
|
||||||
|
public static final String MESSAGE_NUMBER_OUT_OF_RANGE_TOO_BIG = "error-number-out-of-range-too-big";
|
||||||
|
|
||||||
public static final String KEY_MIN = "min";
|
public static final String KEY_MIN = "min";
|
||||||
public static final String KEY_MAX = "max";
|
public static final String KEY_MAX = "max";
|
||||||
|
@ -111,18 +113,31 @@ public abstract class AbstractNumberValidator extends AbstractSimpleValidator im
|
||||||
Number max = getMinMaxConfig(config, KEY_MAX);
|
Number max = getMinMaxConfig(config, KEY_MAX);
|
||||||
|
|
||||||
if (min != null && isFirstGreaterThanToSecond(min, number)) {
|
if (min != null && isFirstGreaterThanToSecond(min, number)) {
|
||||||
context.addError(new ValidationError(getId(), inputHint, MESSAGE_NUMBER_OUT_OF_RANGE, min, max));
|
context.addError(new ValidationError(getId(), inputHint, selectRangeErrorMessage(config), min, max));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max != null && isFirstGreaterThanToSecond(number, max)) {
|
if (max != null && isFirstGreaterThanToSecond(number, max)) {
|
||||||
context.addError(new ValidationError(getId(), inputHint, MESSAGE_NUMBER_OUT_OF_RANGE, min, max));
|
context.addError(new ValidationError(getId(), inputHint, selectRangeErrorMessage(config), min, max));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select error message depending on the allowed range interval bound configuration.
|
||||||
|
*/
|
||||||
|
protected String selectRangeErrorMessage(ValidatorConfig config) {
|
||||||
|
if (!config.containsKey(KEY_MAX)) {
|
||||||
|
return MESSAGE_NUMBER_OUT_OF_RANGE_TOO_SMALL;
|
||||||
|
} else if (!config.containsKey(KEY_MIN)) {
|
||||||
|
return MESSAGE_NUMBER_OUT_OF_RANGE_TOO_BIG;
|
||||||
|
} else {
|
||||||
|
return MESSAGE_NUMBER_OUT_OF_RANGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
|
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
|
||||||
Set<ValidationError> errors = new LinkedHashSet<>();
|
Set<ValidationError> errors = new LinkedHashSet<>();
|
||||||
|
|
|
@ -45,6 +45,8 @@ public class LengthValidator extends AbstractStringValidator implements Configur
|
||||||
public static final String ID = "length";
|
public static final String ID = "length";
|
||||||
|
|
||||||
public static final String MESSAGE_INVALID_LENGTH = "error-invalid-length";
|
public static final String MESSAGE_INVALID_LENGTH = "error-invalid-length";
|
||||||
|
public static final String MESSAGE_INVALID_LENGTH_TOO_SHORT = "error-invalid-length-too-short";
|
||||||
|
public static final String MESSAGE_INVALID_LENGTH_TOO_LONG = "error-invalid-length-too-long";
|
||||||
|
|
||||||
public static final String KEY_MIN = "min";
|
public static final String KEY_MIN = "min";
|
||||||
public static final String KEY_MAX = "max";
|
public static final String KEY_MAX = "max";
|
||||||
|
@ -85,17 +87,30 @@ public class LengthValidator extends AbstractStringValidator implements Configur
|
||||||
int length = value.length();
|
int length = value.length();
|
||||||
|
|
||||||
if (config.containsKey(KEY_MIN) && length < min.intValue()) {
|
if (config.containsKey(KEY_MIN) && length < min.intValue()) {
|
||||||
context.addError(new ValidationError(ID, inputHint, MESSAGE_INVALID_LENGTH, min, max));
|
context.addError(new ValidationError(ID, inputHint, selectErrorMessage(config), min, max));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.containsKey(KEY_MAX) && length > max.intValue()) {
|
if (config.containsKey(KEY_MAX) && length > max.intValue()) {
|
||||||
context.addError(new ValidationError(ID, inputHint, MESSAGE_INVALID_LENGTH, min, max));
|
context.addError(new ValidationError(ID, inputHint, selectErrorMessage(config), min, max));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select error message depending on the allowed length interval bound configuration.
|
||||||
|
*/
|
||||||
|
protected String selectErrorMessage(ValidatorConfig config) {
|
||||||
|
if (!config.containsKey(KEY_MAX)) {
|
||||||
|
return MESSAGE_INVALID_LENGTH_TOO_SHORT;
|
||||||
|
} else if (!config.containsKey(KEY_MIN)) {
|
||||||
|
return MESSAGE_INVALID_LENGTH_TOO_LONG;
|
||||||
|
} else {
|
||||||
|
return MESSAGE_INVALID_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
|
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
|
||||||
|
|
||||||
|
|
|
@ -42,20 +42,29 @@ public class PatternValidator extends AbstractStringValidator implements Configu
|
||||||
|
|
||||||
public static final PatternValidator INSTANCE = new PatternValidator();
|
public static final PatternValidator INSTANCE = new PatternValidator();
|
||||||
|
|
||||||
public static final String KEY_PATTERN = "pattern";
|
public static final String CFG_PATTERN = "pattern";
|
||||||
|
|
||||||
public static final String MESSAGE_NO_MATCH = "error-pattern-no-match";
|
public static final String MESSAGE_NO_MATCH = "error-pattern-no-match";
|
||||||
|
|
||||||
|
public static final String CFG_ERROR_MESSAGE = "error-message";
|
||||||
|
|
||||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(KEY_PATTERN);
|
property.setName(CFG_PATTERN);
|
||||||
property.setLabel("RegExp pattern");
|
property.setLabel("RegExp pattern");
|
||||||
property.setHelpText("RegExp pattern the value must match. Java Pattern syntax is used.");
|
property.setHelpText("RegExp pattern the value must match. Java Pattern syntax is used.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
|
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(CFG_ERROR_MESSAGE);
|
||||||
|
property.setLabel("Error message key");
|
||||||
|
property.setHelpText("Key of the error message in i18n bundle. Dafault message key is " + MESSAGE_NO_MATCH);
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -65,10 +74,10 @@ public class PatternValidator extends AbstractStringValidator implements Configu
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
|
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
|
||||||
Pattern pattern = config.getPattern(KEY_PATTERN);
|
Pattern pattern = config.getPattern(CFG_PATTERN);
|
||||||
|
|
||||||
if (!pattern.matcher(value).matches()) {
|
if (!pattern.matcher(value).matches()) {
|
||||||
context.addError(new ValidationError(ID, inputHint, MESSAGE_NO_MATCH, config.getString(KEY_PATTERN)));
|
context.addError(new ValidationError(ID, inputHint, config.getStringOrDefault(CFG_ERROR_MESSAGE, MESSAGE_NO_MATCH), config.getString(CFG_PATTERN)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,17 +85,17 @@ public class PatternValidator extends AbstractStringValidator implements Configu
|
||||||
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
|
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
|
||||||
Set<ValidationError> errors = new LinkedHashSet<>();
|
Set<ValidationError> errors = new LinkedHashSet<>();
|
||||||
|
|
||||||
if (config == null || config == ValidatorConfig.EMPTY || !config.containsKey(KEY_PATTERN)) {
|
if (config == null || config == ValidatorConfig.EMPTY || !config.containsKey(CFG_PATTERN)) {
|
||||||
errors.add(new ValidationError(ID, KEY_PATTERN, ValidatorConfigValidator.MESSAGE_CONFIG_MISSING_VALUE));
|
errors.add(new ValidationError(ID, CFG_PATTERN, ValidatorConfigValidator.MESSAGE_CONFIG_MISSING_VALUE));
|
||||||
} else {
|
} else {
|
||||||
Object maybePattern = config.get(KEY_PATTERN);
|
Object maybePattern = config.get(CFG_PATTERN);
|
||||||
try {
|
try {
|
||||||
Pattern pattern = config.getPattern(KEY_PATTERN);
|
Pattern pattern = config.getPattern(CFG_PATTERN);
|
||||||
if (pattern == null) {
|
if (pattern == null) {
|
||||||
errors.add(new ValidationError(ID, KEY_PATTERN, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_VALUE, maybePattern));
|
errors.add(new ValidationError(ID, CFG_PATTERN, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_VALUE, maybePattern));
|
||||||
}
|
}
|
||||||
} catch (PatternSyntaxException pse) {
|
} catch (PatternSyntaxException pse) {
|
||||||
errors.add(new ValidationError(ID, KEY_PATTERN, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_VALUE, maybePattern));
|
errors.add(new ValidationError(ID, CFG_PATTERN, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_VALUE, maybePattern, pse.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ValidationResult(errors);
|
return new ValidationResult(errors);
|
||||||
|
|
|
@ -60,6 +60,12 @@ public class BuiltinValidatorsTest {
|
||||||
|
|
||||||
// test value trimming disabled in config
|
// test value trimming disabled in config
|
||||||
Assert.assertTrue("trim disabled but performed", validator.validate("t ", "name", configFromMap(ImmutableMap.of(LengthValidator.KEY_MIN, 2, LengthValidator.KEY_TRIM_DISABLED, true))).isValid());
|
Assert.assertTrue("trim disabled but performed", validator.validate("t ", "name", configFromMap(ImmutableMap.of(LengthValidator.KEY_MIN, 2, LengthValidator.KEY_TRIM_DISABLED, true))).isValid());
|
||||||
|
|
||||||
|
//test correct error message selection
|
||||||
|
Assert.assertEquals(LengthValidator.MESSAGE_INVALID_LENGTH_TOO_SHORT,validator.validate("", "name", ValidatorConfig.builder().config(LengthValidator.KEY_MIN, 1).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(LengthValidator.MESSAGE_INVALID_LENGTH,validator.validate("", "name", ValidatorConfig.builder().config(LengthValidator.KEY_MIN, 1).config(LengthValidator.KEY_MAX, 10).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(LengthValidator.MESSAGE_INVALID_LENGTH_TOO_LONG,validator.validate("aaa", "name", ValidatorConfig.builder().config(LengthValidator.KEY_MAX, 1).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(LengthValidator.MESSAGE_INVALID_LENGTH,validator.validate("aaa", "name", ValidatorConfig.builder().config(LengthValidator.KEY_MIN, 1).config(LengthValidator.KEY_MAX, 2).build()).getErrors().iterator().next().getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -243,6 +249,11 @@ public class BuiltinValidatorsTest {
|
||||||
Assert.assertTrue(validator.validate("100.1", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10.1).config(DoubleValidator.KEY_MAX, 100.1).build()).isValid());
|
Assert.assertTrue(validator.validate("100.1", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10.1).config(DoubleValidator.KEY_MAX, 100.1).build()).isValid());
|
||||||
Assert.assertFalse(validator.validate("100.2", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10.1).config(DoubleValidator.KEY_MAX, 100.1).build()).isValid());
|
Assert.assertFalse(validator.validate("100.2", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10.1).config(DoubleValidator.KEY_MAX, 100.1).build()).isValid());
|
||||||
|
|
||||||
|
//test correct error message selection
|
||||||
|
Assert.assertEquals(DoubleValidator.MESSAGE_NUMBER_OUT_OF_RANGE_TOO_SMALL,validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 100).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(DoubleValidator.MESSAGE_NUMBER_OUT_OF_RANGE,validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 100).config(DoubleValidator.KEY_MAX, 1000).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(DoubleValidator.MESSAGE_NUMBER_OUT_OF_RANGE,validator.validate("10000", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 100).config(DoubleValidator.KEY_MAX, 1000).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(DoubleValidator.MESSAGE_NUMBER_OUT_OF_RANGE_TOO_BIG,validator.validate("10000", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MAX, 1000).build()).getErrors().iterator().next().getMessage());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,28 +329,34 @@ public class BuiltinValidatorsTest {
|
||||||
Assert.assertFalse(validator.validate(Arrays.asList("10", new Object()), "notANumberPresent").isValid());
|
Assert.assertFalse(validator.validate(Arrays.asList("10", new Object()), "notANumberPresent").isValid());
|
||||||
|
|
||||||
// min only
|
// min only
|
||||||
Assert.assertTrue(validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).build()).isValid());
|
Assert.assertTrue(validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).build()).isValid());
|
||||||
Assert.assertFalse(validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 100).build()).isValid());
|
Assert.assertFalse(validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 100).build()).isValid());
|
||||||
// min behavior around empty values
|
// min behavior around empty values
|
||||||
Assert.assertFalse(validator.validate(null, "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).build()).isValid());
|
Assert.assertFalse(validator.validate(null, "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).build()).isValid());
|
||||||
Assert.assertFalse(validator.validate("", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).build()).isValid());
|
Assert.assertFalse(validator.validate("", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).build()).isValid());
|
||||||
Assert.assertFalse(validator.validate(" ", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).build()).isValid());
|
Assert.assertFalse(validator.validate(" ", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).build()).isValid());
|
||||||
Assert.assertTrue(validator.validate(null, "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).config(valConfigIgnoreEmptyValues).build()).isValid());
|
Assert.assertTrue(validator.validate(null, "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).config(valConfigIgnoreEmptyValues).build()).isValid());
|
||||||
Assert.assertTrue(validator.validate("", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).config(valConfigIgnoreEmptyValues).build()).isValid());
|
Assert.assertTrue(validator.validate("", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).config(valConfigIgnoreEmptyValues).build()).isValid());
|
||||||
Assert.assertTrue(validator.validate(" ", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 1).config(valConfigIgnoreEmptyValues).build()).isValid());
|
Assert.assertTrue(validator.validate(" ", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 1).config(valConfigIgnoreEmptyValues).build()).isValid());
|
||||||
|
|
||||||
// max only
|
// max only
|
||||||
Assert.assertFalse(validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MAX, 1).build()).isValid());
|
Assert.assertFalse(validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MAX, 1).build()).isValid());
|
||||||
Assert.assertTrue(validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MAX, 100).build()).isValid());
|
Assert.assertTrue(validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MAX, 100).build()).isValid());
|
||||||
|
|
||||||
// min and max
|
// min and max
|
||||||
Assert.assertFalse(validator.validate("9", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10).config(DoubleValidator.KEY_MAX, 100).build()).isValid());
|
Assert.assertFalse(validator.validate("9", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 10).config(IntegerValidator.KEY_MAX, 100).build()).isValid());
|
||||||
Assert.assertTrue(validator.validate("10", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10).config(DoubleValidator.KEY_MAX, 100).build()).isValid());
|
Assert.assertTrue(validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 10).config(IntegerValidator.KEY_MAX, 100).build()).isValid());
|
||||||
Assert.assertTrue(validator.validate("100", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10).config(DoubleValidator.KEY_MAX, 100).build()).isValid());
|
Assert.assertTrue(validator.validate("100", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 10).config(IntegerValidator.KEY_MAX, 100).build()).isValid());
|
||||||
Assert.assertFalse(validator.validate("101", "name", ValidatorConfig.builder().config(DoubleValidator.KEY_MIN, 10).config(DoubleValidator.KEY_MAX, 100).build()).isValid());
|
Assert.assertFalse(validator.validate("101", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 10).config(IntegerValidator.KEY_MAX, 100).build()).isValid());
|
||||||
|
|
||||||
Assert.assertTrue(validator.validate(Long.MIN_VALUE, "name").isValid());
|
Assert.assertTrue(validator.validate(Long.MIN_VALUE, "name").isValid());
|
||||||
Assert.assertTrue(validator.validate(Long.MAX_VALUE, "name").isValid());
|
Assert.assertTrue(validator.validate(Long.MAX_VALUE, "name").isValid());
|
||||||
|
|
||||||
|
//test correct error message selection
|
||||||
|
Assert.assertEquals(IntegerValidator.MESSAGE_NUMBER_OUT_OF_RANGE_TOO_SMALL,validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 100).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(IntegerValidator.MESSAGE_NUMBER_OUT_OF_RANGE,validator.validate("10", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 100).config(IntegerValidator.KEY_MAX, 1000).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(IntegerValidator.MESSAGE_NUMBER_OUT_OF_RANGE,validator.validate("10000", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MIN, 100).config(IntegerValidator.KEY_MAX, 1000).build()).getErrors().iterator().next().getMessage());
|
||||||
|
Assert.assertEquals(IntegerValidator.MESSAGE_NUMBER_OUT_OF_RANGE_TOO_BIG,validator.validate("10000", "name", ValidatorConfig.builder().config(IntegerValidator.KEY_MAX, 1000).build()).getErrors().iterator().next().getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -384,15 +401,19 @@ public class BuiltinValidatorsTest {
|
||||||
Validator validator = Validators.patternValidator();
|
Validator validator = Validators.patternValidator();
|
||||||
|
|
||||||
// Pattern object in the configuration
|
// Pattern object in the configuration
|
||||||
ValidatorConfig config = configFromMap(Collections.singletonMap(PatternValidator.KEY_PATTERN, Pattern.compile("^start-.*-end$")));
|
ValidatorConfig config = configFromMap(Collections.singletonMap(PatternValidator.CFG_PATTERN, Pattern.compile("^start-.*-end$")));
|
||||||
Assert.assertTrue(validator.validate("start-1234-end", "value", config).isValid());
|
Assert.assertTrue(validator.validate("start-1234-end", "value", config).isValid());
|
||||||
Assert.assertFalse(validator.validate("start___end", "value", config).isValid());
|
Assert.assertFalse(validator.validate("start___end", "value", config).isValid());
|
||||||
|
|
||||||
// String in the configuration
|
// String in the configuration
|
||||||
config = configFromMap(Collections.singletonMap(PatternValidator.KEY_PATTERN, "^start-.*-end$"));
|
config = configFromMap(Collections.singletonMap(PatternValidator.CFG_PATTERN, "^start-.*-end$"));
|
||||||
Assert.assertTrue(validator.validate("start-1234-end", "value", config).isValid());
|
Assert.assertTrue(validator.validate("start-1234-end", "value", config).isValid());
|
||||||
Assert.assertFalse(validator.validate("start___end", "value", config).isValid());
|
Assert.assertFalse(validator.validate("start___end", "value", config).isValid());
|
||||||
|
|
||||||
|
//custom error message
|
||||||
|
config = ValidatorConfig.builder().config(PatternValidator.CFG_PATTERN, "^start-.*-end$").config(PatternValidator.CFG_ERROR_MESSAGE, "customError").build();
|
||||||
|
Assert.assertEquals("customError", validator.validate("start___end", "value", config).getErrors().iterator().next().getMessage());
|
||||||
|
|
||||||
// null and empty values handling
|
// null and empty values handling
|
||||||
Assert.assertFalse(validator.validate(null, "value", config).isValid());
|
Assert.assertFalse(validator.validate(null, "value", config).isValid());
|
||||||
Assert.assertFalse(validator.validate("", "value", config).isValid());
|
Assert.assertFalse(validator.validate("", "value", config).isValid());
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class ValidatorTest {
|
||||||
Assert.assertNotNull(error);
|
Assert.assertNotNull(error);
|
||||||
Assert.assertEquals(LengthValidator.ID, error.getValidatorId());
|
Assert.assertEquals(LengthValidator.ID, error.getValidatorId());
|
||||||
Assert.assertEquals(inputHint, error.getInputHint());
|
Assert.assertEquals(inputHint, error.getInputHint());
|
||||||
Assert.assertEquals(LengthValidator.MESSAGE_INVALID_LENGTH, error.getMessage());
|
Assert.assertEquals(LengthValidator.MESSAGE_INVALID_LENGTH_TOO_SHORT, error.getMessage());
|
||||||
Assert.assertEquals(new Integer(2), error.getMessageParameters()[0]);
|
Assert.assertEquals(new Integer(2), error.getMessageParameters()[0]);
|
||||||
|
|
||||||
Assert.assertTrue(result.hasErrorsForValidatorId(LengthValidator.ID));
|
Assert.assertTrue(result.hasErrorsForValidatorId(LengthValidator.ID));
|
||||||
|
|
|
@ -302,10 +302,11 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
||||||
new AttributeValidatorMetadata(DuplicateUsernameValidator.ID),
|
new AttributeValidatorMetadata(DuplicateUsernameValidator.ID),
|
||||||
new AttributeValidatorMetadata(UsernameMutationValidator.ID)).setAttributeDisplayName("${username}");
|
new AttributeValidatorMetadata(UsernameMutationValidator.ID)).setAttributeDisplayName("${username}");
|
||||||
|
|
||||||
metadata.addAttribute(UserModel.EMAIL, -1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, false)),
|
metadata.addAttribute(UserModel.EMAIL, -1,
|
||||||
new AttributeValidatorMetadata(EmailValidator.ID, ValidatorConfig.builder().config(EmailValidator.IGNORE_EMPTY_VALUE, true).build()),
|
new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, false)),
|
||||||
new AttributeValidatorMetadata(DuplicateEmailValidator.ID),
|
new AttributeValidatorMetadata(DuplicateEmailValidator.ID),
|
||||||
new AttributeValidatorMetadata(EmailExistsAsUsernameValidator.ID)).setAttributeDisplayName("${email}");
|
new AttributeValidatorMetadata(EmailExistsAsUsernameValidator.ID))
|
||||||
|
.setAttributeDisplayName("${email}");
|
||||||
|
|
||||||
List<AttributeValidatorMetadata> readonlyValidators = new ArrayList<>();
|
List<AttributeValidatorMetadata> readonlyValidators = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -326,8 +327,9 @@ public abstract class AbstractUserProfileProvider<U extends UserProfileProvider>
|
||||||
metadata.addAttribute(UserModel.USERNAME, -2, AbstractUserProfileProvider::editUsernameCondition,
|
metadata.addAttribute(UserModel.USERNAME, -2, AbstractUserProfileProvider::editUsernameCondition,
|
||||||
AbstractUserProfileProvider::readUsernameCondition, new AttributeValidatorMetadata(BrokeringFederatedUsernameHasValueValidator.ID)).setAttributeDisplayName("${username}");
|
AbstractUserProfileProvider::readUsernameCondition, new AttributeValidatorMetadata(BrokeringFederatedUsernameHasValueValidator.ID)).setAttributeDisplayName("${username}");
|
||||||
|
|
||||||
metadata.addAttribute(UserModel.EMAIL, -1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, true)),
|
metadata.addAttribute(UserModel.EMAIL, -1,
|
||||||
new AttributeValidatorMetadata(EmailValidator.ID)).setAttributeDisplayName("${email}");
|
new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_EMAIL, true)))
|
||||||
|
.setAttributeDisplayName("${email}");
|
||||||
|
|
||||||
List<AttributeValidatorMetadata> readonlyValidators = new ArrayList<>();
|
List<AttributeValidatorMetadata> readonlyValidators = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ import org.keycloak.userprofile.validator.BlankAttributeValidator;
|
||||||
import org.keycloak.userprofile.validator.ImmutableAttributeValidator;
|
import org.keycloak.userprofile.validator.ImmutableAttributeValidator;
|
||||||
import org.keycloak.validate.AbstractSimpleValidator;
|
import org.keycloak.validate.AbstractSimpleValidator;
|
||||||
import org.keycloak.validate.ValidatorConfig;
|
import org.keycloak.validate.ValidatorConfig;
|
||||||
|
import org.keycloak.validate.validators.EmailValidator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link UserProfileProvider} loading configuration from the changeable JSON file stored in component config. Parsed
|
* {@link UserProfileProvider} loading configuration from the changeable JSON file stored in component config. Parsed
|
||||||
|
@ -142,6 +143,13 @@ public class DeclarativeUserProfileProvider extends AbstractUserProfileProvider<
|
||||||
decoratedMetadata.addAttribute(UserModel.FIRST_NAME, 1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(
|
decoratedMetadata.addAttribute(UserModel.FIRST_NAME, 1, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(
|
||||||
Messages.MISSING_FIRST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${firstName}");
|
Messages.MISSING_FIRST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${firstName}");
|
||||||
decoratedMetadata.addAttribute(UserModel.LAST_NAME, 2, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${lastName}");
|
decoratedMetadata.addAttribute(UserModel.LAST_NAME, 2, new AttributeValidatorMetadata(BlankAttributeValidator.ID, BlankAttributeValidator.createConfig(Messages.MISSING_LAST_NAME, metadata.getContext() == UserProfileContext.IDP_REVIEW))).setAttributeDisplayName("${lastName}");
|
||||||
|
|
||||||
|
//add email format validator to legacy profile
|
||||||
|
List<AttributeMetadata> em = decoratedMetadata.getAttribute(UserModel.EMAIL);
|
||||||
|
for(AttributeMetadata e: em) {
|
||||||
|
e.addValidator(new AttributeValidatorMetadata(EmailValidator.ID, ValidatorConfig.builder().config(EmailValidator.IGNORE_EMPTY_VALUE, true).build()));
|
||||||
|
}
|
||||||
|
|
||||||
return decoratedMetadata;
|
return decoratedMetadata;
|
||||||
}
|
}
|
||||||
return decoratedMetadata;
|
return decoratedMetadata;
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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.userprofile.validator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This validator disallowing bunch of characters we really not to expect in names of persons (fist, middle, last names).
|
||||||
|
* <p>
|
||||||
|
* Validates against hardcoded RegEx pattern - accepts plain string and collection of strings, for basic behavior
|
||||||
|
* like null/blank values handling and collections support see {@link AbstractStringValidator}.
|
||||||
|
*/
|
||||||
|
public class PersonNameProhibitedCharactersValidator extends AbstractStringValidator implements ConfiguredProvider {
|
||||||
|
|
||||||
|
public static final String ID = "person-name-prohibited-characters";
|
||||||
|
|
||||||
|
public static final PersonNameProhibitedCharactersValidator INSTANCE = new PersonNameProhibitedCharactersValidator();
|
||||||
|
|
||||||
|
protected static final Pattern PATTERN = Pattern.compile("^[^<>&\"\\v$%!#?§;*~/\\\\|^=\\[\\]{}()\\p{Cntrl}]+$");
|
||||||
|
|
||||||
|
public static final String MESSAGE_NO_MATCH = "error-person-name-invalid-character";
|
||||||
|
|
||||||
|
public static final String CFG_ERROR_MESSAGE = "error-message";
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(CFG_ERROR_MESSAGE);
|
||||||
|
property.setLabel("Error message key");
|
||||||
|
property.setHelpText("Key of the error message in i18n bundle. Dafault message key is " + MESSAGE_NO_MATCH);
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
|
||||||
|
if (!PATTERN.matcher(value).matches()) {
|
||||||
|
context.addError(new ValidationError(ID, inputHint, config.getStringOrDefault(CFG_ERROR_MESSAGE, MESSAGE_NO_MATCH)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Basic person name (First, Middle, Last name) validator disallowing bunch of characters we really do not expect in names.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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.userprofile.validator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This validator disallowing bunch of characters we really not to expect in username.
|
||||||
|
* <p>
|
||||||
|
* Validates against hardcoded RegEx pattern - accepts plain string and collection of strings, for basic behavior
|
||||||
|
* like null/blank values handling and collections support see {@link AbstractStringValidator}.
|
||||||
|
*/
|
||||||
|
public class UsernameProhibitedCharactersValidator extends AbstractStringValidator implements ConfiguredProvider {
|
||||||
|
|
||||||
|
public static final String ID = "username-prohibited-characters";
|
||||||
|
|
||||||
|
public static final UsernameProhibitedCharactersValidator INSTANCE = new UsernameProhibitedCharactersValidator();
|
||||||
|
|
||||||
|
protected static final Pattern PATTERN = Pattern.compile("^[^<>&\"'\\s\\v\\h$%!#?§,;:*~/\\\\|^=\\[\\]{}()`\\p{Cntrl}]+$");
|
||||||
|
|
||||||
|
public static final String MESSAGE_NO_MATCH = "error-username-invalid-character";
|
||||||
|
|
||||||
|
public static final String CFG_ERROR_MESSAGE = "error-message";
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(CFG_ERROR_MESSAGE);
|
||||||
|
property.setLabel("Error message key");
|
||||||
|
property.setHelpText("Key of the error message in i18n bundle. Dafault message key is " + MESSAGE_NO_MATCH);
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
|
||||||
|
if (!PATTERN.matcher(value).matches()) {
|
||||||
|
context.addError(new ValidationError(ID, inputHint, config.getStringOrDefault(CFG_ERROR_MESSAGE, MESSAGE_NO_MATCH)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Basic Username validator disallowing bunch of characters we really do not expect in username.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,3 +11,5 @@ org.keycloak.userprofile.validator.RegistrationUsernameExistsValidator
|
||||||
org.keycloak.userprofile.validator.RegistrationEmailAsUsernameEmailValueValidator
|
org.keycloak.userprofile.validator.RegistrationEmailAsUsernameEmailValueValidator
|
||||||
org.keycloak.userprofile.validator.BrokeringFederatedUsernameHasValueValidator
|
org.keycloak.userprofile.validator.BrokeringFederatedUsernameHasValueValidator
|
||||||
org.keycloak.userprofile.validator.ImmutableAttributeValidator
|
org.keycloak.userprofile.validator.ImmutableAttributeValidator
|
||||||
|
org.keycloak.userprofile.validator.UsernameProhibitedCharactersValidator
|
||||||
|
org.keycloak.userprofile.validator.PersonNameProhibitedCharactersValidator
|
|
@ -2,11 +2,19 @@
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
"name": "username",
|
||||||
"displayName": "${username}"
|
"displayName": "${username}",
|
||||||
|
"validations": {
|
||||||
|
"length": { "min": 3, "max": 255 },
|
||||||
|
"username-prohibited-characters": {}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "email",
|
"name": "email",
|
||||||
"displayName": "${email}"
|
"displayName": "${email}",
|
||||||
|
"validations": {
|
||||||
|
"email" : {},
|
||||||
|
"length": { "max": 255 }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firstName",
|
"name": "firstName",
|
||||||
|
@ -15,6 +23,10 @@
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"view": ["admin", "user"],
|
"view": ["admin", "user"],
|
||||||
"edit": ["admin", "user"]
|
"edit": ["admin", "user"]
|
||||||
|
},
|
||||||
|
"validations": {
|
||||||
|
"length": { "max": 255 },
|
||||||
|
"person-name-prohibited-characters": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -24,6 +36,10 @@
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"view": ["admin", "user"],
|
"view": ["admin", "user"],
|
||||||
"edit": ["admin", "user"]
|
"edit": ["admin", "user"]
|
||||||
|
},
|
||||||
|
"validations": {
|
||||||
|
"length": { "max": 255 },
|
||||||
|
"person-name-prohibited-characters": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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.userprofile.validator;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlastimil Elias <velias@redhat.com>
|
||||||
|
*/
|
||||||
|
public class PersonNameProhibitedCharactersValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allowed() {
|
||||||
|
// letters and numbers
|
||||||
|
assertValid("a");
|
||||||
|
assertValid("A");
|
||||||
|
assertValid("z");
|
||||||
|
assertValid("Z");
|
||||||
|
assertValid("0");
|
||||||
|
assertValid("9");
|
||||||
|
//other than ASCII alphabets
|
||||||
|
assertValid("\u010D");
|
||||||
|
assertValid("\u01B1");
|
||||||
|
assertValid("\u0397");
|
||||||
|
assertValid("\u98CE\u7720");
|
||||||
|
|
||||||
|
// symbols we want to be allowed
|
||||||
|
assertValid(" ");
|
||||||
|
assertValid(".");
|
||||||
|
assertValid("-");
|
||||||
|
assertValid("_");
|
||||||
|
assertValid("@");
|
||||||
|
assertValid("'");
|
||||||
|
assertValid(":");
|
||||||
|
assertValid(",");
|
||||||
|
|
||||||
|
assertValid("as tr");
|
||||||
|
|
||||||
|
//crazy but existing name ;-)
|
||||||
|
assertValid("X \u00C6 A-12");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void disallowed() {
|
||||||
|
|
||||||
|
// white and control characters
|
||||||
|
assertInvalid("\t");
|
||||||
|
assertInvalid("\n");
|
||||||
|
assertInvalid("\f");
|
||||||
|
assertInvalid("\r");
|
||||||
|
assertInvalid("\u0000");
|
||||||
|
|
||||||
|
//symbols dangerous for distinct technologies or really unnecessary in names
|
||||||
|
//potential path traversals
|
||||||
|
assertInvalid("/");
|
||||||
|
assertInvalid("\\");
|
||||||
|
//html/javascript dangerous
|
||||||
|
assertInvalid("<");
|
||||||
|
assertInvalid(">");
|
||||||
|
assertInvalid("\"");
|
||||||
|
assertInvalid("&");
|
||||||
|
//other symbols not expected in names and potentially dangerous for other technologies
|
||||||
|
assertInvalid("*");
|
||||||
|
assertInvalid("$");
|
||||||
|
assertInvalid("%");
|
||||||
|
assertInvalid("#");
|
||||||
|
assertInvalid("(");
|
||||||
|
assertInvalid(")");
|
||||||
|
assertInvalid("{");
|
||||||
|
assertInvalid("}");
|
||||||
|
assertInvalid("|");
|
||||||
|
assertInvalid("~");
|
||||||
|
assertInvalid("^");
|
||||||
|
assertInvalid("!");
|
||||||
|
assertInvalid("?");
|
||||||
|
assertInvalid(";");
|
||||||
|
assertInvalid("§");
|
||||||
|
assertInvalid("=");
|
||||||
|
|
||||||
|
//unexpected character between expected
|
||||||
|
assertInvalid("as\ttr");
|
||||||
|
assertInvalid("\tastr");
|
||||||
|
assertInvalid("astr\t");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertValid(String value) {
|
||||||
|
Assert.assertTrue(PersonNameProhibitedCharactersValidator.INSTANCE.validate(value).isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInvalid(String value) {
|
||||||
|
Assert.assertFalse(PersonNameProhibitedCharactersValidator.INSTANCE.validate(value).isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* 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.userprofile.validator;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlastimil Elias <velias@redhat.com>
|
||||||
|
*/
|
||||||
|
public class UsernameProhibitedCharactersValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allowed() {
|
||||||
|
// letters and numbers
|
||||||
|
assertValid("a");
|
||||||
|
assertValid("A");
|
||||||
|
assertValid("z");
|
||||||
|
assertValid("Z");
|
||||||
|
assertValid("0");
|
||||||
|
assertValid("9");
|
||||||
|
assertValid("\u010D");
|
||||||
|
assertValid("\u01B1");
|
||||||
|
assertValid("\u0397");
|
||||||
|
|
||||||
|
// symbols we want to be allowed
|
||||||
|
assertValid(".");
|
||||||
|
assertValid("-");
|
||||||
|
assertValid("_");
|
||||||
|
assertValid("@");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void disallowed() {
|
||||||
|
|
||||||
|
// white and control characters
|
||||||
|
assertInvalid(" ");
|
||||||
|
assertInvalid("\t");
|
||||||
|
assertInvalid("\n");
|
||||||
|
assertInvalid("\f");
|
||||||
|
assertInvalid("\r");
|
||||||
|
assertInvalid("\u0000");
|
||||||
|
|
||||||
|
//symbols dangerous for distinct technologies or really unnecessary in username
|
||||||
|
//potential path traversals
|
||||||
|
assertInvalid("/");
|
||||||
|
assertInvalid("\\");
|
||||||
|
//html/javascript dangerous
|
||||||
|
assertInvalid("<");
|
||||||
|
assertInvalid(">");
|
||||||
|
assertInvalid("'");
|
||||||
|
assertInvalid("\"");
|
||||||
|
assertInvalid("&");
|
||||||
|
//other symbols not expected in username and potentially dangerous for other technologies
|
||||||
|
assertInvalid("*");
|
||||||
|
assertInvalid("$");
|
||||||
|
assertInvalid("%");
|
||||||
|
assertInvalid("#");
|
||||||
|
assertInvalid("(");
|
||||||
|
assertInvalid(")");
|
||||||
|
assertInvalid("{");
|
||||||
|
assertInvalid("}");
|
||||||
|
assertInvalid("|");
|
||||||
|
assertInvalid("`");
|
||||||
|
assertInvalid("~");
|
||||||
|
assertInvalid("^");
|
||||||
|
assertInvalid("!");
|
||||||
|
assertInvalid("?");
|
||||||
|
assertInvalid(":");
|
||||||
|
assertInvalid(",");
|
||||||
|
assertInvalid(";");
|
||||||
|
assertInvalid("§");
|
||||||
|
assertInvalid("=");
|
||||||
|
|
||||||
|
//unexpected character between expected
|
||||||
|
assertInvalid("as tr");
|
||||||
|
assertInvalid("\tastr");
|
||||||
|
assertInvalid("astr\t");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertValid(String value) {
|
||||||
|
Assert.assertTrue(UsernameProhibitedCharactersValidator.INSTANCE.validate(value).isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInvalid(String value) {
|
||||||
|
Assert.assertFalse(UsernameProhibitedCharactersValidator.INSTANCE.validate(value).isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -111,7 +111,6 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
||||||
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).detail(Details.PREVIOUS_FIRST_NAME, "Tom").detail(Details.UPDATED_FIRST_NAME, "New first")
|
||||||
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last")
|
.detail(Details.PREVIOUS_LAST_NAME, "Brady").detail(Details.UPDATED_LAST_NAME, "New last")
|
||||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com")
|
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com")
|
||||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com")
|
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
|
|
@ -320,13 +320,16 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "registerUserSuccess@email", "registerUserSuccess", "password", "password");
|
//contains few special characters we want to be sure they are allowed in username
|
||||||
|
String username = "register.U-se@rS_uccess";
|
||||||
|
|
||||||
|
registerPage.register("firstName", "lastName", "registerUserSuccess@email", username, "password", "password");
|
||||||
|
|
||||||
appPage.assertCurrent();
|
appPage.assertCurrent();
|
||||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId();
|
String userId = events.expectRegister(username, "registerUserSuccess@email").assertEvent().getUserId();
|
||||||
assertUserRegistered(userId, "registerusersuccess", "registerusersuccess@email");
|
assertUserRegistered(userId, username.toLowerCase(), "registerusersuccess@email");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertUserRegistered(String userId, String username, String email) {
|
private void assertUserRegistered(String userId, String username, String email) {
|
||||||
|
|
|
@ -758,7 +758,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
fail("Should fail validation");
|
fail("Should fail validation");
|
||||||
} catch (ValidationException ve) {
|
} catch (ValidationException ve) {
|
||||||
assertTrue(ve.isAttributeOnError(UserModel.USERNAME));
|
assertTrue(ve.isAttributeOnError(UserModel.USERNAME));
|
||||||
assertTrue(ve.hasError(LengthValidator.MESSAGE_INVALID_LENGTH));
|
assertTrue(ve.hasError(LengthValidator.MESSAGE_INVALID_LENGTH_TOO_SHORT));
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.put(UserModel.USERNAME, "user");
|
attributes.put(UserModel.USERNAME, "user");
|
||||||
|
@ -769,7 +769,7 @@ public class UserProfileTest extends AbstractUserProfileTest {
|
||||||
|
|
||||||
provider.setConfiguration(null);
|
provider.setConfiguration(null);
|
||||||
|
|
||||||
attributes.put(UserModel.USERNAME, "us");
|
attributes.put(UserModel.USERNAME, "user");
|
||||||
attributes.put(UserModel.FIRST_NAME, "Joe");
|
attributes.put(UserModel.FIRST_NAME, "Joe");
|
||||||
attributes.put(UserModel.LAST_NAME, "Doe");
|
attributes.put(UserModel.LAST_NAME, "Doe");
|
||||||
|
|
||||||
|
|
|
@ -380,12 +380,19 @@ error-invalid-value=Invalid value.
|
||||||
error-invalid-blank=Please specify value.
|
error-invalid-blank=Please specify value.
|
||||||
error-empty=Please specify value.
|
error-empty=Please specify value.
|
||||||
error-invalid-length=Attribute {0} must have a length between {1} and {2}.
|
error-invalid-length=Attribute {0} must have a length between {1} and {2}.
|
||||||
|
error-invalid-length-too-short=Attribute {0} must have minimal length of {1}.
|
||||||
|
error-invalid-length-too-long=Attribute {0} must have maximal length of {2}.
|
||||||
error-invalid-email=Invalid email address.
|
error-invalid-email=Invalid email address.
|
||||||
error-invalid-number=Invalid number.
|
error-invalid-number=Invalid number.
|
||||||
error-number-out-of-range=Attribute {0} must be a number between {1} and {2}.
|
error-number-out-of-range=Attribute {0} must be a number between {1} and {2}.
|
||||||
|
error-number-out-of-range-too-small=Attribute {0} must have minimal value of {1}.
|
||||||
|
error-number-out-of-range-too-big=Attribute {0} must have maximal value of {2}.
|
||||||
error-pattern-no-match=Invalid value.
|
error-pattern-no-match=Invalid value.
|
||||||
error-invalid-uri=Invalid URL.
|
error-invalid-uri=Invalid URL.
|
||||||
error-invalid-uri-scheme=Invalid URL scheme.
|
error-invalid-uri-scheme=Invalid URL scheme.
|
||||||
error-invalid-uri-fragment=Invalid URL fragment.
|
error-invalid-uri-fragment=Invalid URL fragment.
|
||||||
error-user-attribute-required=Please specify attribute {0}.
|
error-user-attribute-required=Please specify attribute {0}.
|
||||||
|
error-invalid-date=Invalid date.
|
||||||
error-user-attribute-read-only=The field {0} is read only.
|
error-user-attribute-read-only=The field {0} is read only.
|
||||||
|
error-username-invalid-character=Username contains invalid character.
|
||||||
|
error-person-name-invalid-character=Name contains invalid character.
|
||||||
|
|
|
@ -45,14 +45,19 @@ error-invalid-value=Invalid value.
|
||||||
error-invalid-blank=Please specify value.
|
error-invalid-blank=Please specify value.
|
||||||
error-empty=Please specify value.
|
error-empty=Please specify value.
|
||||||
error-invalid-length=Attribute {0} must have a length between {1} and {2}.
|
error-invalid-length=Attribute {0} must have a length between {1} and {2}.
|
||||||
|
error-invalid-length-too-short=Attribute {0} must have minimal length of {1}.
|
||||||
|
error-invalid-length-too-long=Attribute {0} must have maximal length of {2}.
|
||||||
error-invalid-email=Invalid email address.
|
error-invalid-email=Invalid email address.
|
||||||
error-invalid-number=Invalid number.
|
error-invalid-number=Invalid number.
|
||||||
error-number-out-of-range=Attribute {0} must be a number between {1} and {2}.
|
error-number-out-of-range=Attribute {0} must be a number between {1} and {2}.
|
||||||
|
error-number-out-of-range-too-small=Attribute {0} must have minimal value of {1}.
|
||||||
|
error-number-out-of-range-too-big=Attribute {0} must have maximal value of {2}.
|
||||||
error-pattern-no-match=Invalid value.
|
error-pattern-no-match=Invalid value.
|
||||||
error-invalid-uri=Invalid URL.
|
error-invalid-uri=Invalid URL.
|
||||||
error-invalid-uri-scheme=Invalid URL scheme.
|
error-invalid-uri-scheme=Invalid URL scheme.
|
||||||
error-invalid-uri-fragment=Invalid URL fragment.
|
error-invalid-uri-fragment=Invalid URL fragment.
|
||||||
error-user-attribute-required=Please specify attribute {0}.
|
error-user-attribute-required=Please specify attribute {0}.
|
||||||
error-invalid-date=Invalid date.
|
error-invalid-date=Attribute {0} is invalid date.
|
||||||
|
|
||||||
error-user-attribute-read-only=Attribute {0} is read only.
|
error-user-attribute-read-only=Attribute {0} is read only.
|
||||||
|
error-username-invalid-character={0} contains invalid character.
|
||||||
|
error-person-name-invalid-character={0} contains invalid character.
|
|
@ -212,15 +212,22 @@ error-invalid-value=Invalid value.
|
||||||
error-invalid-blank=Please specify value.
|
error-invalid-blank=Please specify value.
|
||||||
error-empty=Please specify value.
|
error-empty=Please specify value.
|
||||||
error-invalid-length=Length must be between {1} and {2}.
|
error-invalid-length=Length must be between {1} and {2}.
|
||||||
|
error-invalid-length-too-short=Minimal length is {1}.
|
||||||
|
error-invalid-length-too-long=Maximal length is {2}.
|
||||||
error-invalid-email=Invalid email address.
|
error-invalid-email=Invalid email address.
|
||||||
error-invalid-number=Invalid number.
|
error-invalid-number=Invalid number.
|
||||||
error-number-out-of-range=Number must be between {1} and {2}.
|
error-number-out-of-range=Number must be between {1} and {2}.
|
||||||
|
error-number-out-of-range-too-small=Number must have minimal value of {1}.
|
||||||
|
error-number-out-of-range-too-big=Number must have maximal value of {2}.
|
||||||
error-pattern-no-match=Invalid value.
|
error-pattern-no-match=Invalid value.
|
||||||
error-invalid-uri=Invalid URL.
|
error-invalid-uri=Invalid URL.
|
||||||
error-invalid-uri-scheme=Invalid URL scheme.
|
error-invalid-uri-scheme=Invalid URL scheme.
|
||||||
error-invalid-uri-fragment=Invalid URL fragment.
|
error-invalid-uri-fragment=Invalid URL fragment.
|
||||||
error-user-attribute-required=Please specify this field.
|
error-user-attribute-required=Please specify this field.
|
||||||
|
error-invalid-date=Invalid date.
|
||||||
error-user-attribute-read-only=This field is read only.
|
error-user-attribute-read-only=This field is read only.
|
||||||
|
error-username-invalid-character=Value contains invalid character.
|
||||||
|
error-person-name-invalid-character=Value contains invalid character.
|
||||||
|
|
||||||
invalidPasswordExistingMessage=Invalid existing password.
|
invalidPasswordExistingMessage=Invalid existing password.
|
||||||
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
|
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
|
||||||
|
|
|
@ -124,11 +124,18 @@ error-invalid-value=''{0}'' has invalid value.
|
||||||
error-invalid-blank=Please specify value of ''{0}''.
|
error-invalid-blank=Please specify value of ''{0}''.
|
||||||
error-empty=Please specify value of ''{0}''.
|
error-empty=Please specify value of ''{0}''.
|
||||||
error-invalid-length=''{0}'' must have a length between {1} and {2}.
|
error-invalid-length=''{0}'' must have a length between {1} and {2}.
|
||||||
|
error-invalid-length-too-short=''{0}'' must have minimal length of {1}.
|
||||||
|
error-invalid-length-too-long=''{0}'' must have maximal length of {2}.
|
||||||
error-invalid-email=Invalid email address.
|
error-invalid-email=Invalid email address.
|
||||||
error-invalid-number=''{0}'' is invalid number.
|
error-invalid-number=''{0}'' is invalid number.
|
||||||
error-number-out-of-range=''{0}'' must be a number between {1} and {2}.
|
error-number-out-of-range=''{0}'' must be a number between {1} and {2}.
|
||||||
|
error-number-out-of-range-too-small=''{0}'' must have minimal value of {1}.
|
||||||
|
error-number-out-of-range-too-big=''{0}'' must have maximal value of {2}.
|
||||||
error-pattern-no-match=''{0}'' doesn''t match required format.
|
error-pattern-no-match=''{0}'' doesn''t match required format.
|
||||||
error-invalid-uri=''{0}'' is invalid URL.
|
error-invalid-uri=''{0}'' is invalid URL.
|
||||||
error-invalid-uri-scheme=''{0}'' has invalid URL scheme.
|
error-invalid-uri-scheme=''{0}'' has invalid URL scheme.
|
||||||
error-invalid-uri-fragment=''{0}'' is invalid URL fragment.
|
error-invalid-uri-fragment=''{0}'' is invalid URL fragment.
|
||||||
error-user-attribute-required=Please specify ''{0}''.
|
error-user-attribute-required=Please specify ''{0}''.
|
||||||
|
error-invalid-date=''{0}'' is invalid date.
|
||||||
|
error-username-invalid-character=''{0}'' contains invalid character.
|
||||||
|
error-person-name-invalid-character='{0}' contains invalid character.
|
||||||
|
|
Loading…
Reference in a new issue