Skip to content

Instantly share code, notes, and snippets.

@atomfrede
Created May 7, 2020 09:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atomfrede/311f8a9c6eb74c5c5226af0481155207 to your computer and use it in GitHub Desktop.
Save atomfrede/311f8a9c6eb74c5c5226af0481155207 to your computer and use it in GitHub Desktop.
@Test
public void testLogins() {
Pattern compile = Pattern.compile(Constants.LOGIN_REGEX);
assertThat(compile.matcher("").matches()).isFalse();
assertThat(compile.matcher("aaaaaaaa@").matches()).isFalse();
assertThat(compile.matcher("admin").matches()).isTrue();
assertThat(compile.matcher("aaaaaaaa@1").matches()).isTrue();
assertThat(compile.matcher("me@example.com").matches()).isTrue();
assertThat(compile.matcher("me@example.top.com").matches()).isTrue();
assertThat(compile.matcher("me+foo@example.top.com").matches()).isTrue();
// Works
StringBuilder sb1 = new StringBuilder();
IntStream.range(0, 30000)
.forEach(it -> sb1.append("a"));
sb1.append("@1");
assertThat(compile.matcher(sb1.toString()).matches()).isTrue();
// Very bad
StringBuilder sb = new StringBuilder();
IntStream.range(0, 30000)
.forEach(it -> sb.append("a"));
sb.append("@");
assertThat(compile.matcher(sb.toString()).matches()).isFalse();
}
@atomfrede
Copy link
Author

atomfrede commented May 7, 2020

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(
    validatedBy = {ValidLoginValidator.class}
)
public @interface ValidLogin {

    String message() default "The login is not valid"; // Do not expose the pattern to the outside world to prevent attackers from trying stuff

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}
public class ValidLoginValidator implements ConstraintValidator<ValidLogin, String> {

    private Pattern pattern;

    @Override
    public void initialize(ValidLogin constraintAnnotation) {
        this.pattern = Pattern.compile(Constants.LOGIN_REGEX);
    }

    @Override
    public boolean isValid(String login, ConstraintValidatorContext constraintValidatorContext) {

        // We do a manual check if the login is valid to prevent catastrophic backtracking issues
        if (StringUtils.isEmpty(login)) {
            return false;
        }

        // Try to split it at '@'
        String[] split = login.split("@");

        if (split.length == 2) {
            // the input is assumed save for catastrophic backtracking
            return pattern.matcher(login).matches();
        } else if (split.length == 1) {
            // This is either 'aaaaaa' or 'aaaaaaa@'
            // while the first is valid, the second is not valid and results in catastrophic backtracking.
            if (StringUtils.endsWith(login, "@")) {
                return false;
            }
            // on this case we just need to match against the part before the @ or the whole as it won't backtrack.
            return pattern.matcher(login).matches();
        } else {
            // more than one '@'
            return false;
        }
    }
}
@NotBlank
@ValidLogin
@Size(min = 1, max = 50)
private String login;
@NotBlank
@ValidLogin
@Size(min = 1, max = 50)
private String login;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment