PasswordProperty.java
package net.hostsharing.hsadminng.hs.validation;
import lombok.Setter;
import net.hostsharing.hsadminng.hash.HashGenerator;
import net.hostsharing.hsadminng.hash.HashGenerator.Algorithm;
import java.util.List;
import java.util.stream.Stream;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExistingEntry;
@Setter
public class PasswordProperty extends StringProperty<PasswordProperty> {
private static final String[] KEY_ORDER = insertNewEntriesAfterExistingEntry(
StringProperty.KEY_ORDER,
"computed",
"hashedUsing");
private Algorithm hashedUsing;
private PasswordProperty(final String propertyName) {
super(propertyName, KEY_ORDER);
undisclosed();
}
public static PasswordProperty passwordProperty(final String propertyName) {
return new PasswordProperty(propertyName);
}
@Override
protected void validate(final List<String> result, final String propValue, final PropertiesProvider propProvider) {
// TODO.legacy: remove after legacy data is migrated
if (HashGenerator.using(hashedUsing).couldBeHash(propValue) && propValue.length() > this.maxLength()) {
// already hashed => do not validate
return;
}
super.validate(result, propValue, propProvider);
validatePassword(result, propValue);
}
public PasswordProperty hashedUsing(final Algorithm algorithm) {
this.hashedUsing = algorithm;
computedBy(
ComputeMode.IN_PREP,
(em, entity) -> ofNullable(entity.getDirectValue(propertyName, String.class))
.map(password -> HashGenerator.using(algorithm).withRandomSalt().hashIfNotYetHashed(password))
.orElse(null));
return self();
}
@Override
protected String simpleTypeName() {
return "password";
}
private void validatePassword(final List<String> result, final String password) {
boolean hasLowerCase = false;
boolean hasUpperCase = false;
boolean hasDigit = false;
boolean hasSpecialChar = false;
boolean containsColon = false;
for (char c : password.toCharArray()) {
if (Character.isLowerCase(c)) {
hasLowerCase = true;
} else if (Character.isUpperCase(c)) {
hasUpperCase = true;
} else if (Character.isDigit(c)) {
hasDigit = true;
} else if (!Character.isLetterOrDigit(c)) {
hasSpecialChar = true;
}
if (c == ':') {
containsColon = true;
}
}
final long groupsCovered = Stream.of(hasLowerCase, hasUpperCase, hasDigit, hasSpecialChar).filter(v -> v).count();
if (groupsCovered < 3) {
result.add(propertyName
+ "' must contain at least one character of at least 3 of the following groups: upper case letters, lower case letters, digits, special characters");
}
if (containsColon) {
result.add(propertyName + "' must not contain colon (':')");
}
}
}