integrate-sha512-password-hashing #68
@ -66,7 +66,7 @@ dependencies {
|
|||||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
|
||||||
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
||||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||||
implementation 'commons-codec:commons-codec:1.17.0'
|
implementation 'net.java.dev.jna:jna:5.8.0'
|
||||||
implementation 'org.modelmapper:modelmapper:3.2.0'
|
implementation 'org.modelmapper:modelmapper:3.2.0'
|
||||||
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
|
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
|
||||||
|
@ -15,7 +15,7 @@ public class MultiValidationException extends ValidationException {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void throwInvalid(final List<String> violations) {
|
public static void throwIfNotEmpty(final List<String> violations) {
|
||||||
if (!violations.isEmpty()) {
|
if (!violations.isEmpty()) {
|
||||||
throw new MultiValidationException(violations);
|
throw new MultiValidationException(violations);
|
||||||
}
|
}
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hash;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import jakarta.validation.ValidationException;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hash.HashProcessor.Algorithm.SHA512;
|
|
||||||
|
|
||||||
public class HashProcessor {
|
|
||||||
|
|
||||||
private static final SecureRandom secureRandom = new SecureRandom();
|
|
||||||
|
|
||||||
public enum Algorithm {
|
|
||||||
SHA512
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Base64.Encoder BASE64 = Base64.getEncoder();
|
|
||||||
private static final String SALT_CHARACTERS =
|
|
||||||
"abcdefghijklmnopqrstuvwxyz" +
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
|
||||||
"0123456789$_";
|
|
||||||
|
|
||||||
private final MessageDigest generator;
|
|
||||||
private byte[] saltBytes;
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public static HashProcessor hashAlgorithm(final Algorithm algorithm) {
|
|
||||||
return new HashProcessor(algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HashProcessor(final Algorithm algorithm) throws NoSuchAlgorithmException {
|
|
||||||
generator = MessageDigest.getInstance(algorithm.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generate(final String password) {
|
|
||||||
final byte[] saltedPasswordDigest = calculateSaltedDigest(password);
|
|
||||||
final byte[] hashBytes = appendSaltToSaltedDigest(saltedPasswordDigest);
|
|
||||||
return BASE64.encodeToString(hashBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] appendSaltToSaltedDigest(final byte[] saltedPasswordDigest) {
|
|
||||||
final byte[] hashBytes = new byte[saltedPasswordDigest.length + 1 + saltBytes.length];
|
|
||||||
System.arraycopy(saltedPasswordDigest, 0, hashBytes, 0, saltedPasswordDigest.length);
|
|
||||||
hashBytes[saltedPasswordDigest.length] = ':';
|
|
||||||
System.arraycopy(saltBytes, 0, hashBytes, saltedPasswordDigest.length+1, saltBytes.length);
|
|
||||||
return hashBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] calculateSaltedDigest(final String password) {
|
|
||||||
generator.reset();
|
|
||||||
generator.update(password.getBytes());
|
|
||||||
generator.update(saltBytes);
|
|
||||||
return generator.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashProcessor withSalt(final byte[] saltBytes) {
|
|
||||||
this.saltBytes = saltBytes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashProcessor withSalt(final String salt) {
|
|
||||||
return withSalt(salt.getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashProcessor withRandomSalt() {
|
|
||||||
final var stringBuilder = new StringBuilder(16);
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
|
||||||
int randomIndex = secureRandom.nextInt(SALT_CHARACTERS.length());
|
|
||||||
stringBuilder.append(SALT_CHARACTERS.charAt(randomIndex));
|
|
||||||
}
|
|
||||||
return withSalt(stringBuilder.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashVerifier withHash(final String hash) {
|
|
||||||
return new HashVerifier(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getLastPart(String input, char delimiter) {
|
|
||||||
final var lastIndex = input.lastIndexOf(delimiter);
|
|
||||||
if (lastIndex == -1) {
|
|
||||||
throw new IllegalArgumentException("cannot determine salt, expected: 'digest:salt', but no ':' found");
|
|
||||||
}
|
|
||||||
return input.substring(lastIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HashVerifier {
|
|
||||||
|
|
||||||
private final String hash;
|
|
||||||
|
|
||||||
public HashVerifier(final String hash) {
|
|
||||||
this.hash = hash;
|
|
||||||
withSalt(getLastPart(new String(Base64.getDecoder().decode(hash)), ':'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void verify(String password) {
|
|
||||||
final var computedHash = hashAlgorithm(SHA512).withSalt(saltBytes).generate(password);
|
|
||||||
if ( !computedHash.equals(hash) ) {
|
|
||||||
throw new ValidationException("invalid password");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,112 @@
|
|||||||
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
|
||||||
|
public class LinuxEtcShadowHashGenerator {
|
||||||
|
|
||||||
|
private static final RandomGenerator random = new SecureRandom();
|
||||||
|
private static final Queue<String> predefinedSalts = new PriorityQueue<>();
|
||||||
|
|
||||||
|
public static final int SALT_LENGTH = 16;
|
||||||
|
|
||||||
|
private final String plaintextPassword;
|
||||||
|
private Algorithm algorithm;
|
||||||
|
|
||||||
|
public enum Algorithm {
|
||||||
|
SHA512("6"),
|
||||||
|
YESCRYPT("y");
|
||||||
|
|
||||||
|
final String prefix;
|
||||||
|
|
||||||
|
Algorithm(final String prefix) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Algorithm byPrefix(final String prefix) {
|
||||||
|
return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String SALT_CHARACTERS =
|
||||||
|
"abcdefghijklmnopqrstuvwxyz" +
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||||
|
"0123456789/.";
|
||||||
|
|
||||||
|
private String salt;
|
||||||
|
|
||||||
|
public static LinuxEtcShadowHashGenerator hash(final String plaintextPassword) {
|
||||||
|
return new LinuxEtcShadowHashGenerator(plaintextPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LinuxEtcShadowHashGenerator(final String plaintextPassword) {
|
||||||
|
this.plaintextPassword = plaintextPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinuxEtcShadowHashGenerator using(final Algorithm algorithm) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void verify(final String givenHash) {
|
||||||
|
final var parts = givenHash.split("\\$");
|
||||||
|
if (parts.length < 3 || parts.length > 5) {
|
||||||
|
throw new IllegalArgumentException("not a " + algorithm.name() + " Linux hash: " + givenHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
algorithm = Algorithm.byPrefix(parts[1]);
|
||||||
|
salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
|
||||||
|
|
||||||
|
if (!generate().equals(givenHash)) {
|
||||||
|
throw new IllegalArgumentException("invalid password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generate() {
|
||||||
|
if (salt == null) {
|
||||||
|
throw new IllegalStateException("no salt given");
|
||||||
|
}
|
||||||
|
if (plaintextPassword == null) {
|
||||||
|
throw new IllegalStateException("no password given");
|
||||||
|
}
|
||||||
|
|
||||||
|
return NativeCryptLibrary.INSTANCE.crypt(plaintextPassword, "$" + algorithm.prefix + "$" + salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nextSalt(final String salt) {
|
||||||
|
predefinedSalts.add(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinuxEtcShadowHashGenerator withSalt(final String salt) {
|
||||||
|
this.salt = salt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinuxEtcShadowHashGenerator 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 = random.nextInt(SALT_CHARACTERS.length());
|
||||||
|
stringBuilder.append(SALT_CHARACTERS.charAt(randomIndex));
|
||||||
|
}
|
||||||
|
return withSalt(stringBuilder.toString());
|
||||||
|
}
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(NativeCryptLibrary.INSTANCE.crypt("given password", "$6$abcdefghijklmno"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface NativeCryptLibrary extends Library {
|
||||||
|
NativeCryptLibrary INSTANCE = Native.load("crypt", NativeCryptLibrary.class);
|
||||||
|
|
||||||
|
String crypt(String password, String salt);
|
||||||
|
}
|
||||||
|
}
|
@ -20,22 +20,23 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
|
|||||||
super(properties);
|
super(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> validate(final HsBookingItemEntity bookingItem) {
|
@Override
|
||||||
|
public List<String> validateEntity(final HsBookingItemEntity bookingItem) {
|
||||||
|
return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> validateContext(final HsBookingItemEntity bookingItem) {
|
||||||
return sequentiallyValidate(
|
return sequentiallyValidate(
|
||||||
() -> validateProperties(bookingItem),
|
|
||||||
() -> optionallyValidate(bookingItem.getParentItem()),
|
() -> optionallyValidate(bookingItem.getParentItem()),
|
||||||
() -> validateAgainstSubEntities(bookingItem)
|
() -> validateAgainstSubEntities(bookingItem)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> validateProperties(final HsBookingItemEntity bookingItem) {
|
|
||||||
return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
||||||
return bookingItem != null
|
return bookingItem != null
|
||||||
? enrich(prefix(bookingItem.toShortString(), ""),
|
? enrich(prefix(bookingItem.toShortString(), ""),
|
||||||
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
|
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
|
||||||
: emptyList();
|
: emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,11 +45,13 @@ public class HsBookingItemEntityValidatorRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
|
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
|
||||||
return HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validate(bookingItem);
|
return HsEntityValidator.sequentiallyValidate(
|
||||||
|
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateEntity(bookingItem),
|
||||||
|
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
|
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
|
||||||
MultiValidationException.throwInvalid(doValidate(entityToSave));
|
MultiValidationException.throwIfNotEmpty(doValidate(entityToSave));
|
||||||
return entityToSave;
|
return entityToSave;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
||||||
|
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityProcessor;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry;
|
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetsApi;
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetsApi;
|
||||||
|
|
||||||
@ -21,11 +22,10 @@ import jakarta.persistence.EntityManager;
|
|||||||
import jakarta.persistence.EntityNotFoundException;
|
import jakarta.persistence.EntityNotFoundException;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.validated;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class HsHostingAssetController implements HsHostingAssetsApi {
|
public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
|
|
||||||
final var entities = assetRepo.findAllByCriteria(debitorUuid, parentAssetUuid, HsHostingAssetType.of(type));
|
final var entities = assetRepo.findAllByCriteria(debitorUuid, parentAssetUuid, HsHostingAssetType.of(type));
|
||||||
|
|
||||||
final var resources = mapper.mapList(entities, HsHostingAssetResource.class);
|
final var resources = mapper.mapList(entities, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
return ResponseEntity.ok(resources);
|
return ResponseEntity.ok(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,16 +70,21 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
|
|
||||||
context.define(currentUser, assumedRoles);
|
context.define(currentUser, assumedRoles);
|
||||||
|
|
||||||
final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
final var entity = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||||
|
|
||||||
final var saved = validated(assetRepo.save(entityToSave));
|
final var mapped = new HsHostingAssetEntityProcessor(entity)
|
||||||
|
.validateEntity()
|
||||||
|
.prepareForSave()
|
||||||
|
.saveUsing(assetRepo::save)
|
||||||
|
.validateContext()
|
||||||
|
.mapUsing(e -> mapper.map(e, HsHostingAssetResource.class))
|
||||||
|
.revampProperties();
|
||||||
|
|
||||||
final var uri =
|
final var uri =
|
||||||
MvcUriComponentsBuilder.fromController(getClass())
|
MvcUriComponentsBuilder.fromController(getClass())
|
||||||
.path("/api/hs/hosting/assets/{id}")
|
.path("/api/hs/hosting/assets/{id}")
|
||||||
.buildAndExpand(saved.getUuid())
|
.buildAndExpand(mapped.getUuid())
|
||||||
.toUri();
|
.toUri();
|
||||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
|
||||||
return ResponseEntity.created(uri).body(mapped);
|
return ResponseEntity.created(uri).body(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,21 +128,18 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
|
|
||||||
context.define(currentUser, assumedRoles);
|
context.define(currentUser, assumedRoles);
|
||||||
|
|
||||||
final var current = assetRepo.findByUuid(assetUuid).orElseThrow();
|
final var entity = assetRepo.findByUuid(assetUuid).orElseThrow();
|
||||||
|
|
||||||
new HsHostingAssetEntityPatcher(em, current).apply(body);
|
new HsHostingAssetEntityPatcher(em, entity).apply(body);
|
||||||
|
|
||||||
// TODO.refa: draft for an alternative API
|
final var mapped = new HsHostingAssetEntityProcessor(entity)
|
||||||
// validate(current) // self-validation, hashing passwords etc.
|
.validateEntity()
|
||||||
// .then(HsHostingAssetEntityValidatorRegistry::prepareForSave) // hashing passwords etc.
|
.prepareForSave()
|
||||||
// .then(assetRepo::save)
|
.saveUsing(assetRepo::save)
|
||||||
// .then(HsHostingAssetEntityValidatorRegistry::validateInContext)
|
.validateContext()
|
||||||
// // In this last step we need the entity and the mapped resource instance,
|
.mapUsing(e -> mapper.map(e, HsHostingAssetResource.class))
|
||||||
// // which is exactly what a postmapper takes as arguments.
|
.revampProperties();
|
||||||
// .then(this::mapToResource) using postProcessProperties to remove write-only + add read-only properties
|
|
||||||
|
|
||||||
final var saved = validated(assetRepo.save(current));
|
|
||||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
|
||||||
return ResponseEntity.ok(mapped);
|
return ResponseEntity.ok(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +157,8 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER
|
@SuppressWarnings("unchecked")
|
||||||
= HsHostingAssetEntityValidatorRegistry::postprocessProperties;
|
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource)
|
||||||
|
-> HsHostingAssetEntityValidatorRegistry.forType(entity.getType())
|
||||||
|
.revampProperties(entity, (Map<String, Object>) resource.getConfig());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||||
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsHostingAssetEntity into a readable API.
|
||||||
|
*/
|
||||||
|
public class HsHostingAssetEntityProcessor {
|
||||||
|
|
||||||
|
private final HsEntityValidator<HsHostingAssetEntity> validator;
|
||||||
|
private HsHostingAssetEntity entity;
|
||||||
|
private HsHostingAssetResource resource;
|
||||||
|
|
||||||
|
public HsHostingAssetEntityProcessor(final HsHostingAssetEntity entity) {
|
||||||
|
this.entity = entity;
|
||||||
|
this.validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// validates the entity itself including its properties
|
||||||
|
public HsHostingAssetEntityProcessor validateEntity() {
|
||||||
|
MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// hashing passwords etc.
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public HsHostingAssetEntityProcessor prepareForSave() {
|
||||||
|
validator.prepareProperties(entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HsHostingAssetEntityProcessor saveUsing(final Function<HsHostingAssetEntity, HsHostingAssetEntity> saveFunction) {
|
||||||
|
entity = saveFunction.apply(entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits)
|
||||||
|
public HsHostingAssetEntityProcessor validateContext() {
|
||||||
|
MultiValidationException.throwIfNotEmpty(validator.validateContext(entity));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// maps entity to JSON resource representation
|
||||||
|
public HsHostingAssetEntityProcessor mapUsing(
|
||||||
|
final Function<HsHostingAssetEntity, HsHostingAssetResource> mapFunction) {
|
||||||
|
resource = mapFunction.apply(entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// removes write-only-properties and ads computed-properties
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public HsHostingAssetResource revampProperties() {
|
||||||
|
final var revampedProps = validator.revampProperties(entity, (Map<String, Object>) resource.getConfig());
|
||||||
|
resource.setConfig(revampedProps);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
@ -45,10 +45,16 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> validate(final HsHostingAssetEntity assetEntity) {
|
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||||
return sequentiallyValidate(
|
return sequentiallyValidate(
|
||||||
() -> validateEntityReferencesAndProperties(assetEntity),
|
() -> validateEntityReferencesAndProperties(assetEntity),
|
||||||
() -> validateIdentifierPattern(assetEntity), // might need proper parentAsset or billingItem
|
() -> validateIdentifierPattern(assetEntity)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> validateContext(final HsHostingAssetEntity assetEntity) {
|
||||||
|
return sequentiallyValidate(
|
||||||
() -> optionallyValidate(assetEntity.getBookingItem()),
|
() -> optionallyValidate(assetEntity.getBookingItem()),
|
||||||
() -> optionallyValidate(assetEntity.getParentAsset()),
|
() -> optionallyValidate(assetEntity.getParentAsset()),
|
||||||
() -> validateAgainstSubEntities(assetEntity)
|
() -> validateAgainstSubEntities(assetEntity)
|
||||||
@ -82,14 +88,14 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
|
|||||||
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
|
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
|
||||||
return assetEntity != null
|
return assetEntity != null
|
||||||
? enrich(prefix(assetEntity.toShortString(), "parentAsset"),
|
? enrich(prefix(assetEntity.toShortString(), "parentAsset"),
|
||||||
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validate(assetEntity))
|
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity))
|
||||||
: emptyList();
|
: emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
||||||
return bookingItem != null
|
return bookingItem != null
|
||||||
? enrich(prefix(bookingItem.toShortString(), "bookingItem"),
|
? enrich(prefix(bookingItem.toShortString(), "bookingItem"),
|
||||||
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
|
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
|
||||||
: emptyList();
|
: emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
|||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -40,22 +39,6 @@ public class HsHostingAssetEntityValidatorRegistry {
|
|||||||
return validators.keySet();
|
return validators.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> doValidate(final HsHostingAssetEntity hostingAsset) {
|
|
||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(hostingAsset.getType());
|
|
||||||
return validator.validate(hostingAsset);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HsHostingAssetEntity validated(final HsHostingAssetEntity entityToSave) {
|
|
||||||
MultiValidationException.throwInvalid(doValidate(entityToSave));
|
|
||||||
return entityToSave;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void postprocessProperties(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) {
|
|
||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
|
|
||||||
final var config = validator.postProcess(entity, asMap(resource));
|
|
||||||
resource.setConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static Map<String, Object> asMap(final HsHostingAssetResource resource) {
|
private static Map<String, Object> asMap(final HsHostingAssetResource resource) {
|
||||||
if (resource.getConfig() instanceof Map map) {
|
if (resource.getConfig() instanceof Map map) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.HashProcessor;
|
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||||
@ -31,7 +31,7 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
|
|||||||
.withDefault("/bin/false"),
|
.withDefault("/bin/false"),
|
||||||
stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir),
|
stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir),
|
||||||
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(),
|
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(),
|
||||||
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(HashProcessor.Algorithm.SHA512).writeOnly());
|
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(LinuxEtcShadowHashGenerator.Algorithm.SHA512).writeOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -96,7 +96,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
|||||||
validateDebitTransaction(requestBody, violations);
|
validateDebitTransaction(requestBody, violations);
|
||||||
validateCreditTransaction(requestBody, violations);
|
validateCreditTransaction(requestBody, violations);
|
||||||
validateAssetValue(requestBody, violations);
|
validateAssetValue(requestBody, violations);
|
||||||
MultiValidationException.throwInvalid(violations);
|
MultiValidationException.throwIfNotEmpty(violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateDebitTransaction(
|
private static void validateDebitTransaction(
|
||||||
|
@ -98,7 +98,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
validateSubscriptionTransaction(requestBody, violations);
|
validateSubscriptionTransaction(requestBody, violations);
|
||||||
validateCancellationTransaction(requestBody, violations);
|
validateCancellationTransaction(requestBody, violations);
|
||||||
validateshareCount(requestBody, violations);
|
validateshareCount(requestBody, violations);
|
||||||
MultiValidationException.throwInvalid(violations);
|
MultiValidationException.throwIfNotEmpty(violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateSubscriptionTransaction(
|
private static void validateSubscriptionTransaction(
|
||||||
|
@ -32,7 +32,8 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
|||||||
return String.join(".", parts);
|
return String.join(".", parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract List<String> validate(final E entity);
|
public abstract List<String> validateEntity(final E entity);
|
||||||
|
public abstract List<String> validateContext(final E entity);
|
||||||
|
|
||||||
public final List<Map<String, Object>> properties() {
|
public final List<Map<String, Object>> properties() {
|
||||||
return Arrays.stream(propertyValidators)
|
return Arrays.stream(propertyValidators)
|
||||||
@ -60,7 +61,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
protected static List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
|
public static List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
|
||||||
return new ArrayList<>(stream(validators)
|
return new ArrayList<>(stream(validators)
|
||||||
.map(Supplier::get)
|
.map(Supplier::get)
|
||||||
.filter(violations -> !violations.isEmpty())
|
.filter(violations -> !violations.isEmpty())
|
||||||
@ -89,13 +90,20 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
|||||||
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> postProcess(final E entity, final Map<String, Object> config) {
|
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<String, Object> revampProperties(final E entity, final Map<String, Object> config) {
|
||||||
final var copy = new HashMap<>(config);
|
final var copy = new HashMap<>(config);
|
||||||
stream(propertyValidators).forEach(p -> {
|
stream(propertyValidators).forEach(p -> {
|
||||||
// FIXME: maybe move to ValidatableProperty.postProcess(...)?
|
if (p.isWriteOnly()) {
|
||||||
if ( p.isWriteOnly()) {
|
|
||||||
copy.remove(p.propertyName);
|
copy.remove(p.propertyName);
|
||||||
} else if (p.isComputed()) {
|
} else if (p.isReadOnly() && p.isComputed()) {
|
||||||
copy.put(p.propertyName, p.compute(entity));
|
copy.put(p.propertyName, p.compute(entity));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.HashProcessor.Algorithm;
|
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.Algorithm;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.hash.HashProcessor.hashAlgorithm;
|
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
|
||||||
import static net.hostsharing.hsadminng.mapper.Array.insertAfterEntry;
|
import static net.hostsharing.hsadminng.mapper.Array.insertAfterEntry;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@ -34,10 +34,9 @@ public class PasswordProperty extends StringProperty<PasswordProperty> {
|
|||||||
|
|
||||||
public PasswordProperty hashedUsing(final Algorithm algorithm) {
|
public PasswordProperty hashedUsing(final Algorithm algorithm) {
|
||||||
this.hashedUsing = algorithm;
|
this.hashedUsing = algorithm;
|
||||||
// FIXME: computedBy is too late, we need preprocess
|
|
||||||
computedBy((entity)
|
computedBy((entity)
|
||||||
-> ofNullable(entity.getDirectValue(propertyName, String.class))
|
-> ofNullable(entity.getDirectValue(propertyName, String.class))
|
||||||
.map(password -> hashAlgorithm(algorithm).withRandomSalt().generate(password))
|
.map(password -> hash(password).using(algorithm).withRandomSalt().generate())
|
||||||
.orElse(null));
|
.orElse(null));
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hash;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hash.HashProcessor.Algorithm.SHA512;
|
|
||||||
import static net.hostsharing.hsadminng.hash.HashProcessor.hashAlgorithm;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
|
||||||
|
|
||||||
class HashProcessorUnitTest {
|
|
||||||
|
|
||||||
final String OTHER_PASSWORD = "other password";
|
|
||||||
final String GIVEN_PASSWORD = "given password";
|
|
||||||
final String GIVEN_PASSWORD_HASH = "foKDNQP0oZo0pjFpss5vNl0kfHOs6MKMaJUUbpJTg6hqI1WY+KbU/PKQIg2xt/mwDMmW5WR0pdUZnTv8RPTfhjprZUNqTXJsUXczQnczYUxE";
|
|
||||||
final String GIVEN_SALT = "given salt";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifiesHashedPasswordWithRandomSalt() {
|
|
||||||
final var hash = hashAlgorithm(SHA512).withRandomSalt().generate(GIVEN_PASSWORD);
|
|
||||||
hashAlgorithm(SHA512).withHash(hash).verify(GIVEN_PASSWORD); // throws exception if wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifiesHashedPasswordWithGivenSalt() {
|
|
||||||
final var hash = hashAlgorithm(SHA512).withSalt(GIVEN_SALT).generate(GIVEN_PASSWORD);
|
|
||||||
|
|
||||||
final var decoded = new String(Base64.getDecoder().decode(hash));
|
|
||||||
assertThat(decoded).endsWith(":" + GIVEN_SALT);
|
|
||||||
hashAlgorithm(SHA512).withHash(hash).verify(GIVEN_PASSWORD); // throws exception if wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void throwsExceptionForInvalidPassword() {
|
|
||||||
final var throwable = catchThrowable(() ->
|
|
||||||
hashAlgorithm(SHA512).withHash(GIVEN_PASSWORD_HASH).verify(OTHER_PASSWORD));
|
|
||||||
|
|
||||||
assertThat(throwable).hasMessage("invalid password");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,51 @@
|
|||||||
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.Algorithm.SHA512;
|
||||||
|
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
class LinuxEtcShadowHashGeneratorUnitTest {
|
||||||
|
|
||||||
|
final String GIVEN_PASSWORD = "given password";
|
||||||
|
final String WRONG_PASSWORD = "wrong password";
|
||||||
|
final String GIVEN_SALT = "0123456789abcdef";
|
||||||
|
|
||||||
|
// generated via mkpasswd for plaintext password GIVEN_PASSWORD (see above)
|
||||||
|
final String GIVEN_SHA512_HASH = "$6$ooei1HK6JXVaI7KC$sY5d9fEOr36hjh4CYwIKLMfRKL1539bEmbVCZ.zPiH0sv7jJVnoIXb5YEefEtoSM2WWgDi9hr7vXRe3Nw8zJP/";
|
||||||
|
final String GIVEN_YESCRYPT_HASH = "$y$j9T$wgYACPmBXvlMg2MzeZA0p1$KXUzd28nG.67GhPnBZ3aZsNNA5bWFdL/dyG4wS0iRw7";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesPasswordAgainstSha512HashFromMkpasswd() {
|
||||||
|
hash(GIVEN_PASSWORD).verify(GIVEN_SHA512_HASH); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesPasswordAgainstYescryptHashFromMkpasswd() {
|
||||||
|
hash(GIVEN_PASSWORD).verify(GIVEN_YESCRYPT_HASH); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesHashedPasswordWithRandomSalt() {
|
||||||
|
final var hash = hash(GIVEN_PASSWORD).using(SHA512).withRandomSalt().generate();
|
||||||
|
hash(GIVEN_PASSWORD).verify(hash); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesHashedPasswordWithGivenSalt() {
|
||||||
|
final var givenPasswordHash =hash(GIVEN_PASSWORD).using(SHA512).withSalt(GIVEN_SALT).generate();
|
||||||
|
hash(GIVEN_PASSWORD).verify(givenPasswordHash); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void throwsExceptionForInvalidPassword() {
|
||||||
|
final var givenPasswordHash = hash(GIVEN_PASSWORD).using(SHA512).withRandomSalt().generate();
|
||||||
|
|
||||||
|
final var throwable = catchThrowable(() ->
|
||||||
|
hash(WRONG_PASSWORD).verify(givenPasswordHash) // throws exception if wrong);
|
||||||
|
);
|
||||||
|
assertThat(throwable).hasMessage("invalid password");
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,12 @@ public class TestHsBookingItem {
|
|||||||
.project(TEST_PROJECT)
|
.project(TEST_PROJECT)
|
||||||
.type(HsBookingItemType.CLOUD_SERVER)
|
.type(HsBookingItemType.CLOUD_SERVER)
|
||||||
.caption("test cloud server booking item")
|
.caption("test cloud server booking item")
|
||||||
|
.resources(Map.ofEntries(
|
||||||
|
entry("CPUs", 2),
|
||||||
|
entry("RAM", 4),
|
||||||
|
entry("SSD", 50),
|
||||||
|
entry("Traffic", 250)
|
||||||
|
))
|
||||||
.validity(Range.closedInfinite(LocalDate.of(2020, 1, 15)))
|
.validity(Range.closedInfinite(LocalDate.of(2020, 1, 15)))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -55,13 +55,13 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=boolean, propertyName=active, required=false, defaultValue=true, isTotalsValidator=false}",
|
"{type=boolean, propertyName=active, defaultValue=true}",
|
||||||
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=CPUs, min=1, max=32, required=true}",
|
||||||
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}",
|
||||||
"{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, required=true}",
|
||||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=false}",
|
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0}",
|
||||||
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true}",
|
||||||
"{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, isTotalsValidator=false}");
|
"{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -63,17 +63,17 @@ class HsManagedServerBookingItemValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=integer, propertyName=CPUs, min=1, max=32, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=CPUs, min=1, max=32, required=true}",
|
||||||
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}",
|
||||||
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}",
|
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}",
|
||||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, required=false, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}",
|
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}",
|
||||||
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}",
|
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}",
|
||||||
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], required=false, defaultValue=BASIC, isTotalsValidator=false}",
|
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], defaultValue=BASIC}",
|
||||||
"{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=false, isTotalsValidator=false}",
|
"{type=boolean, propertyName=SLA-EMail}", // TODO.impl: falseIf-validation is missing in output
|
||||||
"{type=boolean, propertyName=SLA-Maria, required=false, isTotalsValidator=false}",
|
"{type=boolean, propertyName=SLA-Maria}",
|
||||||
"{type=boolean, propertyName=SLA-PgSQL, required=false, isTotalsValidator=false}",
|
"{type=boolean, propertyName=SLA-PgSQL}",
|
||||||
"{type=boolean, propertyName=SLA-Office, required=false, isTotalsValidator=false}",
|
"{type=boolean, propertyName=SLA-Office}",
|
||||||
"{type=boolean, propertyName=SLA-Web, required=false, isTotalsValidator=false}");
|
"{type=boolean, propertyName=SLA-Web}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -55,12 +55,12 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true}",
|
||||||
"{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10, required=false, isTotalsValidator=false}",
|
"{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10}",
|
||||||
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true, isTotalsValidator=false}",
|
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true}",
|
||||||
"{type=integer, propertyName=Multi, min=1, max=100, step=1, required=false, defaultValue=1, isTotalsValidator=false}",
|
"{type=integer, propertyName=Multi, min=1, max=100, step=1, defaultValue=1}",
|
||||||
"{type=integer, propertyName=Daemons, min=0, max=10, required=false, defaultValue=0, isTotalsValidator=false}",
|
"{type=integer, propertyName=Daemons, min=0, max=10, defaultValue=0}",
|
||||||
"{type=boolean, propertyName=Online Office Server, required=false, isTotalsValidator=false}",
|
"{type=boolean, propertyName=Online Office Server}",
|
||||||
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], required=false, defaultValue=BASIC, isTotalsValidator=false}");
|
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], defaultValue=BASIC}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
|
|||||||
import io.restassured.RestAssured;
|
import io.restassured.RestAssured;
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||||
|
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
@ -523,6 +524,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
.identifier("fir01-temp")
|
.identifier("fir01-temp")
|
||||||
.caption("some test-unix-user")
|
.caption("some test-unix-user")
|
||||||
.build());
|
.build());
|
||||||
|
LinuxEtcShadowHashGenerator.nextSalt("Jr5w/Y8zo8pCkqg7");
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
@ -575,7 +577,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
assertThat(asset.getCaption()).isEqualTo("some patched test-unix-user");
|
assertThat(asset.getCaption()).isEqualTo("some patched test-unix-user");
|
||||||
assertThat(asset.getConfig().toString()).isEqualTo("""
|
assertThat(asset.getConfig().toString()).isEqualTo("""
|
||||||
{
|
{
|
||||||
"password": "Ein Passwort mit 4 Zeichengruppen!",
|
"password": "$6$Jr5w/Y8zo8pCkqg7$/rePRbvey3R6Sz/02YTlTQcRt5qdBPTj2h5.hz.rB8NfIoND8pFOjeB7orYcPs9JNf3JDxPP2V.6MQlE5BwAY/",
|
||||||
"shell": "/bin/bash",
|
"shell": "/bin/bash",
|
||||||
"totpKey": "0x1234567890abcdef0123456789abcdef"
|
"totpKey": "0x1234567890abcdef0123456789abcdef"
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(cloudServerHostingAssetEntity);
|
final var result = validator.validateEntity(cloudServerHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
@ -49,7 +49,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(cloudServerHostingAssetEntity);
|
final var result = validator.validateEntity(cloudServerHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
@ -76,7 +76,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedServerHostingAssetEntity);
|
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
@ -96,7 +96,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedServerHostingAssetEntity);
|
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
import static org.assertj.core.api.Assertions.entry;
|
|
||||||
|
|
||||||
class HsHostingAssetEntityValidatorRegistryUnitTest {
|
class HsHostingAssetEntityValidatorRegistryUnitTest {
|
||||||
|
|
||||||
@ -41,24 +35,4 @@ class HsHostingAssetEntityValidatorRegistryUnitTest {
|
|||||||
HsHostingAssetType.UNIX_USER
|
HsHostingAssetType.UNIX_USER
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void validatedDoesNotThrowAnExceptionForValidEntity() {
|
|
||||||
final var givenBookingItem = HsBookingItemEntity.builder()
|
|
||||||
.type(HsBookingItemType.CLOUD_SERVER)
|
|
||||||
.resources(Map.ofEntries(
|
|
||||||
entry("CPUs", 4),
|
|
||||||
entry("RAM", 20),
|
|
||||||
entry("SSD", 50),
|
|
||||||
entry("Traffic", 250)
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
final var validEntity = HsHostingAssetEntity.builder()
|
|
||||||
.type(HsHostingAssetType.CLOUD_SERVER)
|
|
||||||
.bookingItem(givenBookingItem)
|
|
||||||
.identifier("vm1234")
|
|
||||||
.caption("some valid cloud server")
|
|
||||||
.build();
|
|
||||||
HsHostingAssetEntityValidatorRegistry.validated(validEntity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
|
||||||
|
|
||||||
class HsHostingAssetEntityValidatorUnitTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void validThrowsException() {
|
|
||||||
// given
|
|
||||||
final var managedServerHostingAssetEntity = HsHostingAssetEntity.builder()
|
|
||||||
.type(MANAGED_SERVER)
|
|
||||||
.identifier("vm1234")
|
|
||||||
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_SERVER).build())
|
|
||||||
.parentAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
|
|
||||||
.assignedToAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// when
|
|
||||||
final var result = catchThrowable( ()-> HsHostingAssetEntityValidatorRegistry.validated(managedServerHostingAssetEntity));
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertThat(result.getMessage()).contains(
|
|
||||||
"'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null",
|
|
||||||
"'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ -19,7 +21,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
|||||||
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
|
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
|
||||||
.type(MANAGED_SERVER)
|
.type(MANAGED_SERVER)
|
||||||
.identifier("vm1234")
|
.identifier("vm1234")
|
||||||
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.MANAGED_SERVER).build())
|
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
|
||||||
.parentAsset(HsHostingAssetEntity.builder().build())
|
.parentAsset(HsHostingAssetEntity.builder().build())
|
||||||
.assignedToAsset(HsHostingAssetEntity.builder().build())
|
.assignedToAsset(HsHostingAssetEntity.builder().build())
|
||||||
.config(Map.ofEntries(
|
.config(Map.ofEntries(
|
||||||
@ -31,12 +33,12 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedWebspaceHostingAssetEntity.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedWebspaceHostingAssetEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
final var result = validator.validateEntity(mangedWebspaceHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-???????-?:null",
|
"'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-1234500:test project:test project booking item",
|
||||||
"'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-???????-?:null",
|
"'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-1234500:test project:test project booking item",
|
||||||
"'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2",
|
"'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2",
|
||||||
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 100 but is 101",
|
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 100 but is 101",
|
||||||
"'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'");
|
"'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'");
|
||||||
@ -53,7 +55,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedServerHostingAssetEntity);
|
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
@ -68,17 +70,17 @@ class HsManagedServerHostingAssetValidatorUnitTest {
|
|||||||
.identifier("xyz00")
|
.identifier("xyz00")
|
||||||
.parentAsset(HsHostingAssetEntity.builder().build())
|
.parentAsset(HsHostingAssetEntity.builder().build())
|
||||||
.assignedToAsset(HsHostingAssetEntity.builder().build())
|
.assignedToAsset(HsHostingAssetEntity.builder().build())
|
||||||
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
|
.bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM)
|
||||||
.build();
|
.build();
|
||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedServerHostingAssetEntity);
|
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER",
|
"'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER",
|
||||||
"'MANAGED_SERVER:xyz00.parentAsset' must be null but is set to D-???????-?:null",
|
"'MANAGED_SERVER:xyz00.parentAsset' must be null but is set to D-1234500:test project:test cloud server booking item",
|
||||||
"'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is set to D-???????-?:null");
|
"'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is set to D-1234500:test project:test cloud server booking item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
||||||
@ -70,7 +71,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
final var result = validator.validateContext(mangedWebspaceHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
@ -88,7 +89,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
final var result = validator.validateEntity(mangedWebspaceHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly("'identifier' expected to match '^abc[0-9][0-9]$', but is 'xyz00'");
|
assertThat(result).containsExactly("'identifier' expected to match '^abc[0-9][0-9]$', but is 'xyz00'");
|
||||||
@ -109,7 +110,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
final var result = validator.validateEntity(mangedWebspaceHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly("'MANAGED_WEBSPACE:abc00.config.unknown' is not expected but is set to 'some value'");
|
assertThat(result).containsExactly("'MANAGED_WEBSPACE:abc00.config.unknown' is not expected but is set to 'some value'");
|
||||||
@ -131,7 +132,10 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
final var result = Stream.concat(
|
||||||
|
validator.validateEntity(mangedWebspaceHostingAssetEntity).stream(),
|
||||||
|
validator.validateContext(mangedWebspaceHostingAssetEntity).stream())
|
||||||
|
.toList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
@ -154,7 +158,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(mangedWebspaceHostingAssetEntity);
|
final var result = validator.validateEntity(mangedWebspaceHostingAssetEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly(
|
assertThat(result).containsExactly(
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.HashMap;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Map.ofEntries;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM;
|
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_WEBSPACE_BOOKING_ITEM;
|
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_WEBSPACE_BOOKING_ITEM;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
||||||
@ -21,32 +24,55 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
.caption("some managed server")
|
.caption("some managed server")
|
||||||
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
|
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
|
||||||
.build();
|
.build();
|
||||||
private HsHostingAssetEntity TEST_MANAGED_WEBSPACE_HOSTING_ASSET = HsHostingAssetEntity.builder()
|
private final HsHostingAssetEntity TEST_MANAGED_WEBSPACE_HOSTING_ASSET = HsHostingAssetEntity.builder()
|
||||||
.type(MANAGED_WEBSPACE)
|
.type(MANAGED_WEBSPACE)
|
||||||
.bookingItem(TEST_MANAGED_WEBSPACE_BOOKING_ITEM)
|
.bookingItem(TEST_MANAGED_WEBSPACE_BOOKING_ITEM)
|
||||||
.parentAsset(TEST_MANAGED_SERVER_HOSTING_ASSET)
|
.parentAsset(TEST_MANAGED_SERVER_HOSTING_ASSET)
|
||||||
.identifier("abc00")
|
.identifier("abc00")
|
||||||
.build();;
|
.build();
|
||||||
|
private final HsHostingAssetEntity GIVEN_VALID_UNIX_USER_HOSTING_ASSET = HsHostingAssetEntity.builder()
|
||||||
|
.type(UNIX_USER)
|
||||||
|
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
||||||
|
.identifier("abc00-temp")
|
||||||
|
.caption("some valid test UnixUser")
|
||||||
|
.config(new HashMap<>(ofEntries(
|
||||||
|
entry("SSD hard quota", 50),
|
||||||
|
entry("SSD soft quota", 40),
|
||||||
|
entry("totpKey", "0x123456789abcdef01234"),
|
||||||
|
entry("password", "Hallo Computer, lass mich rein!")
|
||||||
|
)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void preparesUnixUser() {
|
||||||
|
// given
|
||||||
|
final var unixUserHostingAsset = GIVEN_VALID_UNIX_USER_HOSTING_ASSET;
|
||||||
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
|
LinuxEtcShadowHashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
||||||
|
validator.prepareProperties(unixUserHostingAsset);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(unixUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||||
|
entry("SSD hard quota", 50),
|
||||||
|
entry("SSD soft quota", 40),
|
||||||
|
entry("totpKey", "0x123456789abcdef01234"),
|
||||||
|
entry("password", "$6$Ly3LbsArtL5u4EVt$i/ayIEvm0y4bjkFB6wbg8imbRIaw4mAA4gqYRVyoSkj.iIxJKS3KiRkSjP8gweNcpKL0Q0N31EadT8fCnWErL.")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validatesValidUnixUser() {
|
void validatesValidUnixUser() {
|
||||||
// given
|
// given
|
||||||
final var unixUserHostingAsset = HsHostingAssetEntity.builder()
|
final var unixUserHostingAsset = GIVEN_VALID_UNIX_USER_HOSTING_ASSET;
|
||||||
.type(UNIX_USER)
|
|
||||||
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
|
||||||
.identifier("abc00-temp")
|
|
||||||
.caption("some valid test UnixUser")
|
|
||||||
.config(Map.ofEntries(
|
|
||||||
entry("SSD hard quota", 50),
|
|
||||||
entry("SSD soft quota", 40),
|
|
||||||
entry("totpKey", "0x123456789abcdef01234"),
|
|
||||||
entry("password", "Hallo Computer, lass mich rein!")
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(unixUserHostingAsset);
|
final var result = Stream.concat(
|
||||||
|
validator.validateEntity(unixUserHostingAsset).stream(),
|
||||||
|
validator.validateContext(unixUserHostingAsset).stream()
|
||||||
|
).toList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
@ -60,7 +86,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
|
||||||
.identifier("abc00-temp")
|
.identifier("abc00-temp")
|
||||||
.caption("some test UnixUser with invalid properties")
|
.caption("some test UnixUser with invalid properties")
|
||||||
.config(Map.ofEntries(
|
.config(ofEntries(
|
||||||
entry("SSD hard quota", 100),
|
entry("SSD hard quota", 100),
|
||||||
entry("SSD soft quota", 200),
|
entry("SSD soft quota", 200),
|
||||||
entry("HDD hard quota", 100),
|
entry("HDD hard quota", 100),
|
||||||
@ -74,7 +100,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(unixUserHostingAsset);
|
final var result = validator.validateEntity(unixUserHostingAsset);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
@ -101,13 +127,31 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = validator.validate(unixUserHostingAsset);
|
final var result = validator.validateEntity(unixUserHostingAsset);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly(
|
assertThat(result).containsExactly(
|
||||||
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'");
|
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void revampsUnixUser() {
|
||||||
|
// given
|
||||||
|
final var unixUserHostingAsset = GIVEN_VALID_UNIX_USER_HOSTING_ASSET;
|
||||||
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
|
// when
|
||||||
|
LinuxEtcShadowHashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
||||||
|
final var result = validator.revampProperties(unixUserHostingAsset, unixUserHostingAsset.getConfig());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(result).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||||
|
entry("SSD hard quota", 50),
|
||||||
|
entry("SSD soft quota", 40),
|
||||||
|
entry("homedir", "/home/pacs/abc00/users/temp")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void describesItsProperties() {
|
void describesItsProperties() {
|
||||||
// given
|
// given
|
||||||
@ -125,7 +169,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
"{type=enumeration, propertyName=shell, values=[/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd], defaultValue=/bin/false}",
|
"{type=enumeration, propertyName=shell, values=[/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd], defaultValue=/bin/false}",
|
||||||
"{type=string, propertyName=homedir, readOnly=true, computed=true}",
|
"{type=string, propertyName=homedir, readOnly=true, computed=true}",
|
||||||
"{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, undisclosed=true}",
|
"{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, undisclosed=true}",
|
||||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, hashedUsing=SHA512, undisclosed=true}"
|
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=SHA512, undisclosed=true}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hash.HashProcessor.Algorithm.SHA512;
|
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.Algorithm.SHA512;
|
||||||
import static net.hostsharing.hsadminng.hash.HashProcessor.hashAlgorithm;
|
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
|
||||||
import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty;
|
import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty;
|
||||||
import static net.hostsharing.hsadminng.mapper.PatchableMapWrapper.entry;
|
import static net.hostsharing.hsadminng.mapper.PatchableMapWrapper.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -115,6 +115,6 @@ class PasswordPropertyUnitTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// then
|
// then
|
||||||
hashAlgorithm(SHA512).withHash(result).verify("some password"); // throws exception if wrong
|
hash("some password").using(SHA512).withRandomSalt().generate(); // throws exception if wrong
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user