diff --git a/src/main/java/net/hostsharing/hsadminng/hash/HashProcessor.java b/src/main/java/net/hostsharing/hsadminng/hash/HashProcessor.java index e6fc4c99..6ef0a49b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hash/HashProcessor.java +++ b/src/main/java/net/hostsharing/hsadminng/hash/HashProcessor.java @@ -2,6 +2,9 @@ package net.hostsharing.hsadminng.hash; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.random.RandomGenerator; import lombok.SneakyThrows; import org.bouncycastle.crypto.generators.OpenBSDBCrypt; @@ -10,7 +13,9 @@ import static net.hostsharing.hsadminng.hash.HashProcessor.Algorithm.SHA512; public class HashProcessor { - private static final SecureRandom secureRandom = new SecureRandom(); + private static final RandomGenerator random = new SecureRandom(); + private static final Queue predefinedSalts = new PriorityQueue<>(); + public static final int SALT_LENGTH = 16; public static final int COST_FACTOR = 13; @@ -60,15 +65,22 @@ public class HashProcessor { return "$6$" + salt + "$" + hashedPassword; } + public static void nextSalt(final String salt) { + predefinedSalts.add(salt); + } + public HashProcessor withSalt(final String salt) { this.salt = salt; return this; } public HashProcessor withRandomSalt() { + if (!predefinedSalts.isEmpty()) { + return withSalt(predefinedSalts.poll()); + } final var stringBuilder = new StringBuilder(SALT_LENGTH); for (int i = 0; i < SALT_LENGTH; ++i) { - int randomIndex = secureRandom.nextInt(SALT_CHARACTERS.length()); + int randomIndex = random.nextInt(SALT_CHARACTERS.length()); stringBuilder.append(SALT_CHARACTERS.charAt(randomIndex)); } return withSalt(stringBuilder.toString()); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java index 8aa51ec8..5e270c86 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityProcessor.java @@ -29,8 +29,9 @@ public class HsHostingAssetEntityProcessor { } /// hashing passwords etc. + @SuppressWarnings("unchecked") public HsHostingAssetEntityProcessor prepareForSave() { - // FIXME: add computed properties + validator.prepareProperties(entity); return this; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index ba21173e..13cb3f05 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -90,12 +90,20 @@ public abstract class HsEntityValidator { throw new IllegalArgumentException("Integer value (or null) expected, but got " + value); } + public void prepareProperties(final E entity) { + stream(propertyValidators).forEach(p -> { + if ( p.isWriteOnly() && p.isComputed()) { + entity.directProps().put(p.propertyName, p.compute(entity)); + } + }); + } + public Map revampProperties(final E entity, final Map config) { final var copy = new HashMap<>(config); stream(propertyValidators).forEach(p -> { - if ( p.isWriteOnly()) { + if (p.isWriteOnly()) { copy.remove(p.propertyName); - } else if (p.isComputed()) { + } else if (p.isReadOnly() && p.isComputed()) { copy.put(p.propertyName, p.compute(entity)); } }); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java index 09cc79b1..f40f5e30 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java @@ -34,7 +34,6 @@ public class PasswordProperty extends StringProperty { public PasswordProperty hashedUsing(final Algorithm algorithm) { this.hashedUsing = algorithm; - // FIXME: computedBy is too late, we need preprocess computedBy((entity) -> ofNullable(entity.getDirectValue(propertyName, String.class)) .map(password -> hashAlgorithm(algorithm).withRandomSalt().generate(password).forLinux()) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 11bfc45c..5743fbfc 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset; import io.restassured.RestAssured; import io.restassured.http.ContentType; import net.hostsharing.hsadminng.HsadminNgApplication; +import net.hostsharing.hsadminng.hash.HashProcessor; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType; @@ -523,6 +524,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup .identifier("fir01-temp") .caption("some test-unix-user") .build()); + HashProcessor.nextSalt("Jr5w/Y8zo8pCkqg7"); RestAssured // @formatter:off .given() @@ -575,7 +577,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup assertThat(asset.getCaption()).isEqualTo("some patched test-unix-user"); assertThat(asset.getConfig().toString()).isEqualTo(""" { - "password": "Ein Passwort mit 4 Zeichengruppen!", + "password": "$6$Jr5w/Y8zo8pCkqg7$JDJ5JDEzJFFsR3pidzdYTUZudE1GL0JZMURsTHVkZUpwTmVZYlM4ZzRqeC95WUo3Y2c5OUpTdUZpbWFx", "shell": "/bin/bash", "totpKey": "0x1234567890abcdef0123456789abcdef" }