1
0

import-unix-user-and-email-aliases (#81)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/81
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-08-01 13:12:58 +02:00
parent e1fda412ae
commit d6a0511d98
50 changed files with 1132 additions and 337 deletions

View File

@@ -39,7 +39,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=string, propertyName=local-part, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], required=true}",
"{type=string, propertyName=sub-domain, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$]}",
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$], maxLength=320}, required=true, minLength=1}");
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$], maxLength=320}, required=true, minLength=1}");
}
@Test
@@ -73,7 +73,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ADDRESS:test@example.org.config.local-part' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowed' does not match",
"'EMAIL_ADDRESS:test@example.org.config.sub-domain' is expected to match any of [^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$] but 'no@allowedeither' does not match",
"'EMAIL_ADDRESS:test@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$] but 'garbage' does not match any");
"'EMAIL_ADDRESS:test@example.org.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$] but 'garbage' does not match any");
}
@Test

View File

@@ -22,18 +22,24 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$], maxLength=320}, required=true, minLength=1}");
"{type=string[], propertyName=target, elementsOf={type=string, propertyName=target, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$, ^:include:/.*$, ^\\|.*$, ^/dev/null$], maxLength=320}, required=true, minLength=1}");
}
@Test
void validatesValidEntity() {
void acceptsValidEntity() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("xyz00-office")
.config(Map.ofEntries(
entry("target", Array.of("xyz00", "xyz00-abc", "office@example.com"))
entry("target", Array.of(
"xyz00",
"xyz00-abc",
"office@example.com",
"/dev/null",
"|/home/pacs/xyz00/mailinglists/ecartis -s xyz00-intern"
))
))
.build();
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
@@ -46,14 +52,22 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
}
@Test
void validatesProperties() {
void rejectsInvalidConfig() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(TEST_MANAGED_WEBSPACE_HOSTING_ASSET)
.identifier("xyz00-office")
.config(Map.ofEntries(
entry("target", Array.of("xyz00", "xyz00-abc", "garbage", "office@example.com"))
entry("target", Array.of(
"/dev/null",
"xyz00",
"xyz00-abc",
"garbage",
"office@example.com",
":include:/home/pacs/xyz00/mailinglists/textfile",
"|/home/pacs/xyz00/mailinglists/executable"
))
))
.build();
final var validator = HostingAssetEntityValidatorRegistry.forType(emailAliasHostingAssetEntity.getType());
@@ -63,11 +77,11 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ALIAS:xyz00-office.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$] but 'garbage' does not match any");
"'EMAIL_ALIAS:xyz00-office.config.target' is expected to match any of [^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$, ^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$, ^:include:/.*$, ^\\|.*$, ^/dev/null$] but 'garbage' does not match any");
}
@Test
void validatesInvalidIdentifier() {
void rejectsInvalidIndentifier() {
// given
final var emailAliasHostingAssetEntity = HsHostingAssetEntity.builder()
.type(EMAIL_ALIAS)
@@ -84,7 +98,7 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'identifier' expected to match '^xyz00$|^xyz00-[a-z0-9]+$', but is 'abc00-office'");
"'identifier' expected to match '^xyz00$|^xyz00-[a-z0-9][a-z0-9\\._-]*$', but is 'abc00-office'");
}
@Test

View File

@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
import org.junit.jupiter.api.Test;
import jakarta.persistence.EntityManager;
import java.util.HashMap;
import java.util.stream.Stream;
@@ -24,6 +25,8 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
.caption("some valid test MariaDB-Instance")
.build();
private EntityManager em = null; // not actually needed in these test cases
private static HsHostingAssetEntityBuilder givenValidMariaDbUserBuilder() {
return HsHostingAssetEntity.builder()
.type(MARIADB_USER)
@@ -46,7 +49,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
// then
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=MYSQL_NATIVE, undisclosed=true}"
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=IN_PREP, hashedUsing=MYSQL_NATIVE, undisclosed=true}"
);
}
@@ -58,7 +61,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
// when
// HashGenerator.nextSalt("Ly3LbsArtL5u4EVt"); // not needed for mysql_native_password
validator.prepareProperties(givenMariaDbUserHostingAsset);
validator.prepareProperties(em, givenMariaDbUserHostingAsset);
// then
assertThat(givenMariaDbUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(

View File

@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
import org.junit.jupiter.api.Test;
import jakarta.persistence.EntityManager;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.HashMap;
@@ -27,6 +28,8 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest {
.caption("some valid test PgSql-Instance")
.build();
private EntityManager em = null; // not actually needed in these test cases
private static HsHostingAssetEntityBuilder givenValidMariaDbUserBuilder() {
return HsHostingAssetEntity.builder()
.type(PGSQL_USER)
@@ -49,7 +52,7 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest {
// then
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=SCRAM_SHA256, undisclosed=true}"
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=IN_PREP, hashedUsing=SCRAM_SHA256, undisclosed=true}"
);
}
@@ -61,7 +64,7 @@ class HsPostgreSqlUserHostingAssetValidatorUnitTest {
// when
HashGenerator.nextSalt(new String(Base64.getDecoder().decode("L1QxSVNyTU81b3NZS1djNg=="), Charset.forName("latin1")));
validator.prepareProperties(givenMariaDbUserHostingAsset);
validator.prepareProperties(em, givenMariaDbUserHostingAsset);
// then
assertThat(givenMariaDbUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(

View File

@@ -3,8 +3,14 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hash.HashGenerator;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.HashMap;
import java.util.stream.Stream;
@@ -15,7 +21,10 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANA
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
import static net.hostsharing.hsadminng.mapper.PatchMap.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
@ExtendWith(MockitoExtension.class)
class HsUnixUserHostingAssetValidatorUnitTest {
private final HsHostingAssetEntity TEST_MANAGED_SERVER_HOSTING_ASSET = HsHostingAssetEntity.builder()
@@ -43,6 +52,18 @@ class HsUnixUserHostingAssetValidatorUnitTest {
)))
.build();
@Mock
EntityManager em;
@BeforeEach
void initMocks() {
final var nativeQueryMock = mock(Query.class);
lenient().when(nativeQueryMock.getSingleResult()).thenReturn(12345678);
lenient().when(em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')", Integer.class))
.thenReturn(nativeQueryMock);
}
@Test
void preparesUnixUser() {
// given
@@ -51,14 +72,15 @@ class HsUnixUserHostingAssetValidatorUnitTest {
// when
HashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
validator.prepareProperties(unixUserHostingAsset);
validator.prepareProperties(em, 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.")
entry("password", "$6$Ly3LbsArtL5u4EVt$i/ayIEvm0y4bjkFB6wbg8imbRIaw4mAA4gqYRVyoSkj.iIxJKS3KiRkSjP8gweNcpKL0Q0N31EadT8fCnWErL."),
entry("userid", 12345678)
));
}
@@ -87,8 +109,8 @@ class HsUnixUserHostingAssetValidatorUnitTest {
.identifier("abc00-temp")
.caption("some test UnixUser with invalid properties")
.config(ofEntries(
entry("SSD hard quota", 100),
entry("SSD soft quota", 200),
entry("SSD hard quota", 60000),
entry("SSD soft quota", 70000),
entry("HDD hard quota", 100),
entry("HDD soft quota", 200),
entry("shell", "/is/invalid"),
@@ -104,11 +126,10 @@ class HsUnixUserHostingAssetValidatorUnitTest {
// then
assertThat(result).containsExactlyInAnyOrder(
"'UNIX_USER:abc00-temp.config.SSD hard quota' is expected to be at most 50 but is 100",
"'UNIX_USER:abc00-temp.config.SSD soft quota' is expected to be at most 100 but is 200",
"'UNIX_USER:abc00-temp.config.SSD hard quota' is expected to be at most 51200 but is 60000",
"'UNIX_USER:abc00-temp.config.SSD soft quota' is expected to be at most 60000 but is 70000",
"'UNIX_USER:abc00-temp.config.HDD hard quota' is expected to be at most 0 but is 100",
"'UNIX_USER:abc00-temp.config.HDD soft quota' is expected to be at most 100 but is 200",
"'UNIX_USER:abc00-temp.config.shell' is expected to be one of [/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd] but is '/is/invalid'",
"'UNIX_USER:abc00-temp.config.homedir' is readonly but given as '/is/read-only'",
"'UNIX_USER:abc00-temp.config.totpKey' is expected to match any of [^0x([0-9A-Fa-f]{2})+$] but provided value does not match",
"'UNIX_USER:abc00-temp.config.password' length is expected to be at min 8 but length of provided value is 5",
@@ -131,7 +152,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
// then
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
@@ -142,7 +163,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
// when
HashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
final var result = validator.revampProperties(unixUserHostingAsset, unixUserHostingAsset.getConfig());
final var result = validator.revampProperties(em, unixUserHostingAsset, unixUserHostingAsset.getConfig());
// then
assertThat(result).containsExactlyInAnyOrderEntriesOf(ofEntries(
@@ -162,14 +183,16 @@ class HsUnixUserHostingAssetValidatorUnitTest {
// then
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD hard quota, unit=GB, maxFrom=SSD}",
"{type=integer, propertyName=SSD soft quota, unit=GB, maxFrom=SSD hard quota}",
"{type=integer, propertyName=HDD hard quota, unit=GB, maxFrom=HDD}",
"{type=integer, propertyName=HDD soft quota, unit=GB, maxFrom=HDD hard quota}",
"{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=boolean, propertyName=locked, readOnly=true}",
"{type=integer, propertyName=userid, readOnly=true, computed=IN_INIT}",
"{type=integer, propertyName=SSD hard quota, unit=MB, maxFrom=SSD}",
"{type=integer, propertyName=SSD soft quota, unit=MB, maxFrom=SSD hard quota}",
"{type=integer, propertyName=HDD hard quota, unit=MB, maxFrom=HDD}",
"{type=integer, propertyName=HDD soft quota, unit=MB, maxFrom=HDD hard quota}",
"{type=string, propertyName=shell, provided=[/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=IN_REVAMP}",
"{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, computed=true, hashedUsing=LINUX_SHA512, undisclosed=true}"
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=IN_PREP, hashedUsing=LINUX_SHA512, undisclosed=true}"
);
}
}

View File

@@ -3,6 +3,8 @@ package net.hostsharing.hsadminng.hs.migration;
import com.opencsv.CSVParserBuilder;
import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
@@ -25,18 +27,24 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.stream.Collectors;
import static java.lang.Boolean.parseBoolean;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.Array.emptyArray;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@@ -68,7 +76,7 @@ public class CsvDataImport extends ContextBasedTest {
@MockBean
HttpServletRequest request;
private static final List<AssertionError> errors = new ArrayList<>();
static final List<String> errors = new ArrayList<>();
public List<String[]> readAllLines(Reader reader) throws Exception {
@@ -113,6 +121,16 @@ public class CsvDataImport extends ContextBasedTest {
return records.subList(1, records.size());
}
@SneakyThrows
public static String[] parseCsvLine(final String csvLine) {
try (final var reader = new CSVReader(new StringReader(csvLine))) {
return stream(ofNullable(reader.readNext()).orElse(emptyArray(String.class)))
.map(String::trim)
.map(target -> target.startsWith("'") && target.endsWith("'") ? target.substring(1, target.length()-1) : target)
.toArray(String[]::new);
}
}
String[] trimAll(final String[] record) {
for (int i = 0; i < record.length; ++i) {
if (record[i] != null) {
@@ -124,23 +142,75 @@ public class CsvDataImport extends ContextBasedTest {
public <T extends RbacObject> T persist(final Integer id, final T entity) {
try {
final var asString = entity.toString();
if ( asString.contains("'null null, null'") || asString.equals("person()")) {
System.err.println("skipping to persist empty record-id " + id + " #" + entity.hashCode() + ": " + entity);
return entity;
if (entity instanceof HsHostingAsset ha) {
//noinspection unchecked
return (T) persistViaSql(id, ha);
}
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
em.persist(entity);
// uncomment for debugging purposes
// em.flush(); // makes it slow, but produces better error messages
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
return persistViaEM(id, entity);
} catch (Exception exc) {
System.err.println("failed to persist #" + entity.hashCode() + ": " + entity);
System.err.println(exc);
errors.add("failed to persist #" + entity.hashCode() + ": " + entity);
errors.add(exc.toString());
}
return entity;
}
public <T extends RbacObject> T persistViaEM(final Integer id, final T entity) {
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
em.persist(entity);
// uncomment for debugging purposes
// em.flush(); // makes it slow, but produces better error messages
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
return entity;
}
@SneakyThrows
public RbacObject<HsHostingAsset> persistViaSql(final Integer id, final HsHostingAsset entity) {
if (entity.getUuid() == null) {
entity.setUuid(UUID.randomUUID());
}
final var query = em.createNativeQuery("""
insert into hs_hosting_asset(
uuid,
type,
bookingitemuuid,
parentassetuuid,
assignedtoassetuuid,
alarmcontactuuid,
identifier,
caption,
config,
version)
values (
:uuid,
:type,
:bookingitemuuid,
:parentassetuuid,
:assignedtoassetuuid,
:alarmcontactuuid,
:identifier,
:caption,
cast(:config as jsonb),
:version)
""")
.setParameter("uuid", entity.getUuid())
.setParameter("type", entity.getType().name())
.setParameter("bookingitemuuid", ofNullable(entity.getBookingItem()).map(RbacObject::getUuid).orElse(null))
.setParameter("parentassetuuid", ofNullable(entity.getParentAsset()).map(RbacObject::getUuid).orElse(null))
.setParameter("assignedtoassetuuid", ofNullable(entity.getAssignedToAsset()).map(RbacObject::getUuid).orElse(null))
.setParameter("alarmcontactuuid", ofNullable(entity.getAlarmContact()).map(RbacObject::getUuid).orElse(null))
.setParameter("identifier", entity.getIdentifier())
.setParameter("caption", entity.getCaption())
.setParameter("config", entity.getConfig().toString())
.setParameter("version", entity.getVersion());
final var count = query.executeUpdate();
logError(() -> {
assertThat(count).isEqualTo(1);
});
return entity;
}
protected <E> String toFormattedString(final Map<Integer, E> map) {
if ( map.isEmpty() ) {
return "{}";
@@ -215,12 +285,33 @@ public class CsvDataImport extends ContextBasedTest {
try {
assertion.run();
} catch (final AssertionError exc) {
errors.add(exc);
errors.add(exc.toString());
}
}
void logErrors() {
assumeThat(errors).isEmpty();
assertThat(errors).isEmpty();
}
void expectErrors(final String... expectedErrors) {
assertContainsExactlyInAnyOrderIgnoringWhitespace(errors, expectedErrors);
}
private static class IgnoringWhitespaceComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.replaceAll("\\s", "").compareTo(s2.replaceAll("\\s", ""));
}
}
public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List<String> expected, final List<String> actual) {
final var sortedExpected = expected.stream().map(m -> m.replaceAll("\\s", "")).toList();
final var sortedActual = actual.stream().map(m -> m.replaceAll("\\s", "")).toArray(String[]::new);
assertThat(sortedExpected).containsExactlyInAnyOrder(sortedActual);
}
public static void assertContainsExactlyInAnyOrderIgnoringWhitespace(final List<String> expected, final String... actual) {
assertContainsExactlyInAnyOrderIgnoringWhitespace(expected, asList(actual));
}
}
@@ -252,7 +343,7 @@ class Record {
}
String getString(final String columnName) {
return row[columns.indexOf(columnName)];
return row[columns.indexOf(columnName)].trim();
}
boolean isEmpty(final String columnName) {
@@ -288,12 +379,17 @@ class Record {
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContinueOnFailure {
}
class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
private static boolean previousTestsPassed = true;
public void testFailed(ExtensionContext context, Throwable cause) {
previousTestsPassed = false;
@Override
public void testFailed(final ExtensionContext context, final Throwable cause) {
previousTestsPassed = previousTestsPassed && context.getElement().map(e -> e.isAnnotationPresent(ContinueOnFailure.class)).orElse(false);
}
@Override

View File

@@ -0,0 +1,114 @@
package net.hostsharing.hsadminng.hs.migration;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Builder
@Entity
@Table(name = "hs_hosting_asset")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class HsHostingAssetRawEntity implements HsHostingAsset {
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "bookingitemuuid")
private HsBookingItemEntity bookingItem;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parentassetuuid")
private HsHostingAssetRawEntity parentAsset;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "assignedtoassetuuid")
private HsHostingAssetRawEntity assignedToAsset;
@Column(name = "type")
@Enumerated(EnumType.STRING)
private HsHostingAssetType type;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "alarmcontactuuid")
private HsOfficeContactEntity alarmContact;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
private List<HsHostingAssetRawEntity> subHostingAssets;
@Column(name = "identifier")
private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc
@Column(name = "caption")
private String caption;
@Builder.Default
@Setter(AccessLevel.NONE)
@Type(JsonType.class)
@Column(columnDefinition = "config")
private Map<String, Object> config = new HashMap<>();
@Transient
private PatchableMapWrapper<Object> configWrapper;
@Transient
private boolean isLoaded;
@PostLoad
public void markAsLoaded() {
this.isLoaded = true;
}
public PatchableMapWrapper<Object> getConfig() {
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config);
}
@Override
public Map<String, Object> directProps() {
return config;
}
@Override
public String toString() {
return stringify.using(HsHostingAssetRawEntity.class).apply(this);
}
}

View File

@@ -6,7 +6,6 @@ import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
@@ -23,18 +22,23 @@ import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.DirtiesContext;
import java.io.Reader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import static java.util.Arrays.stream;
import static java.util.Map.entry;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ALIAS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.IPV4_NUMBER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
@@ -91,13 +95,15 @@ public class ImportHostingAssets extends ImportOfficeData {
static final Integer IP_NUMBER_ID_OFFSET = 1000000;
static final Integer HIVE_ID_OFFSET = 2000000;
static final Integer PACKET_ID_OFFSET = 3000000;
static final Integer UNIXUSER_ID_OFFSET = 4000000;
static final Integer EMAILALIAS_ID_OFFSET = 5000000;
record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference<HsHostingAssetEntity> serverRef) {}
record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference<HsHostingAssetRawEntity> serverRef) {}
static Map<Integer, HsBookingProjectEntity> bookingProjects = new WriteOnceMap<>();
static Map<Integer, HsBookingItemEntity> bookingItems = new WriteOnceMap<>();
static Map<Integer, Hive> hives = new WriteOnceMap<>();
static Map<Integer, HsHostingAssetEntity> hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type?
static Map<Integer, HsHostingAssetRawEntity> hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type?
@Test
@Order(11010)
@@ -126,14 +132,13 @@ public class ImportHostingAssets extends ImportOfficeData {
void verifyIpNumbers() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(firstOfEachType(5, IPV4_NUMBER)).isEqualToIgnoringWhitespace("""
{
1000363=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.34),
1000381=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.52),
1000402=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.73),
1000433=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.104),
1000457=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.128)
1000363=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.34),
1000381=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.52),
1000402=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.73),
1000433=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.104),
1000457=HsHostingAssetRawEntity(IPV4_NUMBER, 83.223.95.128)
}
""");
}
@@ -154,7 +159,6 @@ public class ImportHostingAssets extends ImportOfficeData {
void verifyHives() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(toFormattedString(first(5, hives))).isEqualToIgnoringWhitespace("""
{
2000001=Hive[hive_id=1, hive_name=h00, inet_addr_id=358, serverRef=null],
@@ -184,13 +188,13 @@ public class ImportHostingAssets extends ImportOfficeData {
assertThat(firstOfEachType(3, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE)).isEqualToIgnoringWhitespace("""
{
3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
3000630=HsHostingAssetRawEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
3000968=HsHostingAssetRawEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
3000978=HsHostingAssetRawEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
3001061=HsHostingAssetRawEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
3001094=HsHostingAssetRawEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
3001112=HsHostingAssetRawEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
3023611=HsHostingAssetRawEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
}
""");
assertThat(firstOfEachType(
@@ -226,19 +230,18 @@ public class ImportHostingAssets extends ImportOfficeData {
void verifyPacketComponents() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(firstOfEachType(5, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE))
.isEqualToIgnoringWhitespace("""
{
3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
3001447=HsHostingAssetEntity(MANAGED_SERVER, vm1093, HA vm1093, D-1000000:hsh default project:BI vm1093),
3019959=HsHostingAssetEntity(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00),
3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
3000630=HsHostingAssetRawEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
3000968=HsHostingAssetRawEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
3000978=HsHostingAssetRawEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
3001061=HsHostingAssetRawEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
3001094=HsHostingAssetRawEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
3001112=HsHostingAssetRawEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
3001447=HsHostingAssetRawEntity(MANAGED_SERVER, vm1093, HA vm1093, D-1000000:hsh default project:BI vm1093),
3019959=HsHostingAssetRawEntity(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00),
3023611=HsHostingAssetRawEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
}
""");
assertThat(firstOfEachType(
@@ -262,58 +265,247 @@ public class ImportHostingAssets extends ImportOfficeData {
}
@Test
@Order(11400)
@Order(14010)
void importUnixUsers() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/unixuser.csv")) {
final var lines = readAllLines(reader);
importUnixUsers(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
@Order(14019)
void verifyUnixUsers() {
assumeThatWeAreImportingControlledTestData();
assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace("""
{
4005803=HsHostingAssetRawEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102090}),
4005805=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102091}),
4005809=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "shell": "/bin/bash", "userid": 102093}),
4005811=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102094}),
4005813=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/usr/bin/passwd", "userid": 102095}),
4005835=HsHostingAssetRawEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "shell": "/usr/bin/passwd", "userid": 102106}),
4005964=HsHostingAssetRawEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102147}),
4005966=HsHostingAssetRawEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "shell": "/bin/bash", "userid": 102148}),
4005990=HsHostingAssetRawEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 102160}),
4100705=HsHostingAssetRawEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 10003}),
4100824=HsHostingAssetRawEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 10000}),
4167846=HsHostingAssetRawEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/false", "userid": 110568}),
4169546=HsHostingAssetRawEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110593}),
4169596=HsHostingAssetRawEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110594})
}
""");
}
@Test
@Order(14020)
void importEmailAliases() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/emailalias.csv")) {
final var lines = readAllLines(reader);
importEmailAliases(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
@Order(14029)
void verifyEmailAliases() {
assumeThatWeAreImportingControlledTestData();
assertThat(firstOfEachType(15, EMAIL_ALIAS)).isEqualToIgnoringWhitespace("""
{
5002403=HsHostingAssetRawEntity(EMAIL_ALIAS, lug00, lug00, MANAGED_WEBSPACE:lug00, { "target": "[michael.mellis@example.com]"}),
5002405=HsHostingAssetRawEntity(EMAIL_ALIAS, lug00-wla-listar, lug00-wla-listar, MANAGED_WEBSPACE:lug00, { "target": "[|/home/pacs/lug00/users/in/mailinglist/listar]"}),
5002429=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00, mim00, MANAGED_WEBSPACE:mim00, { "target": "[mim12-mi@mim12.hostsharing.net]"}),
5002431=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-abruf, mim00-abruf, MANAGED_WEBSPACE:mim00, { "target": "[michael.mellis@hostsharing.net]"}),
5002449=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-hhfx, mim00-hhfx, MANAGED_WEBSPACE:mim00, { "target": "[mim00-hhfx, |/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l]"}),
5002451=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-hhfx-l, mim00-hhfx-l, MANAGED_WEBSPACE:mim00, { "target": "[:include:/home/pacs/mim00/etc/hhfx.list]"}),
5002452=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-empty, mim00-empty, MANAGED_WEBSPACE:mim00, { "target": "[]"}),
5002453=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-0_entries, mim00-0_entries, MANAGED_WEBSPACE:mim00, { "target": "[]"}),
5002454=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-dev.null, mim00-dev.null, MANAGED_WEBSPACE:mim00, { "target": "[/dev/null]"}),
5002455=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-1_with_space, mim00-1_with_space, MANAGED_WEBSPACE:mim00, { "target": "[|/home/pacs/mim00/install/corpslistar/listar]"}),
5002456=HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-1_with_single_quotes, mim00-1_with_single_quotes, MANAGED_WEBSPACE:mim00, { "target": "[|/home/pacs/rir00/mailinglist/ecartis -r kybs06-intern]"})
}
""");
}
// --------------------------------------------------------------------------------------------
@Test
@Order(18010)
void validateBookingItems() {
bookingItems.forEach((id, bi) -> {
try {
HsBookingItemEntityValidatorRegistry.validated(bi);
} catch (final Exception exc) {
System.err.println("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage());
errors.add("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage());
}
});
}
@Test
@Order(11410)
@Order(18020)
void validateHostingAssets() {
hostingAssets.forEach((id, ha) -> {
try {
new HostingAssetEntitySaveProcessor(ha)
new HostingAssetEntitySaveProcessor(em, ha)
.preprocessEntity()
.validateEntity();
.validateEntity()
.prepareForSave();
} catch (final Exception exc) {
System.err.println("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage());
errors.add("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage());
}
});
}
@Test
@Order(18999)
@ContinueOnFailure
void logValidationErrors() {
this.logErrors();
}
// --------------------------------------------------------------------------------------------
@Test
@Order(19000)
@Commit
void persistHostingAssetEntities() {
void persistBookingProjects() {
System.out.println("PERSISTING hosting-assets to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
System.out.println("PERSISTING booking-projects to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
bookingProjects.forEach(this::persist);
}).assertSuccessful();
}
@Test
@Order(19010)
@Commit
void persistBookingItems() {
System.out.println("PERSISTING booking-items to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
bookingItems.forEach(this::persistRecursively);
}).assertSuccessful();
}
@Test
@Order(19120)
@Commit
void persistCloudServers() {
System.out.println("PERSISTING cloud-servers to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
persistHostingAssetsOfType(CLOUD_SERVER);
}
@Test
@Order(19130)
@Commit
void persistManagedServers() {
System.out.println("PERSISTING managed-servers to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
persistHostingAssetsOfType(MANAGED_SERVER);
}
@Test
@Order(19140)
@Commit
void persistManagedWebspaces() {
System.out.println("PERSISTING managed-webspaces to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
persistHostingAssetsOfType(MANAGED_WEBSPACE);
}
@Test
@Order(19150)
@Commit
void persistIPNumbers() {
System.out.println("PERSISTING ip-numbers to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
persistHostingAssetsOfType(IPV4_NUMBER);
}
@Test
@Order(19160)
@Commit
void persistUnixUsers() {
System.out.println("PERSISTING unix-users to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
persistHostingAssetsOfType(UNIX_USER);
}
@Test
@Order(19170)
@Commit
void persistEmailAliases() {
System.out.println("PERSISTING email-aliases to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
persistHostingAssetsOfType(EMAIL_ALIAS);
}
@Test
@Order(19900)
void verifyPersistedUnixUsersWithUserId() {
assumeThatWeAreImportingControlledTestData();
assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace("""
{
4005803=HsHostingAssetRawEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102090}),
4005805=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.1, Paul Klemm, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102091}),
4005809=HsHostingAssetRawEntity(UNIX_USER, lug00-wla.2, Walter Müller, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 8, "SSD soft quota": 4, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102093}),
4005811=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.a, LUG OLA - POP a, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102094}),
4005813=HsHostingAssetRawEntity(UNIX_USER, lug00-ola.b, LUG OLA - POP b, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102095}),
4005835=HsHostingAssetRawEntity(UNIX_USER, lug00-test, Test, MANAGED_WEBSPACE:lug00, { "SSD hard quota": 1024, "SSD soft quota": 1024, "locked": false, "password": null, "shell": "/usr/bin/passwd", "userid": 102106}),
4005964=HsHostingAssetRawEntity(UNIX_USER, mim00, Michael Mellis, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102147}),
4005966=HsHostingAssetRawEntity(UNIX_USER, mim00-1981, Jahrgangstreffen 1981, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 256, "SSD soft quota": 128, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102148}),
4005990=HsHostingAssetRawEntity(UNIX_USER, mim00-mail, Mailbox, MANAGED_WEBSPACE:mim00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 102160}),
4100705=HsHostingAssetRawEntity(UNIX_USER, hsh00-mim, Michael Mellis, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 10003}),
4100824=HsHostingAssetRawEntity(UNIX_USER, hsh00, Hostsharing Paket, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 10000}),
4167846=HsHostingAssetRawEntity(UNIX_USER, hsh00-dph, hsh00-uph, MANAGED_WEBSPACE:hsh00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/false", "userid": 110568}),
4169546=HsHostingAssetRawEntity(UNIX_USER, dph00, Reinhard Wiese, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 110593}),
4169596=HsHostingAssetRawEntity(UNIX_USER, dph00-uph, Domain admin, MANAGED_WEBSPACE:dph00, { "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 110594})
}
""");
}
@Test
@Order(19910)
void verifyBookingItemsAreActuallyPersisted() {
final var biCount = (Integer) em.createNativeQuery("SELECT count(*) FROM hs_booking_item", Integer.class).getSingleResult();
assertThat(biCount).isGreaterThan(isImportingControlledTestData() ? 5 : 500);
}
@Test
@Order(19920)
void verifyHostingAssetsAreActuallyPersisted() {
final var haCount = (Integer) em.createNativeQuery("SELECT count(*) FROM hs_hosting_asset", Integer.class).getSingleResult();
assertThat(haCount).isGreaterThan(isImportingControlledTestData() ? 20 : 10000);
}
// ============================================================================================
@Test
@Order(99999)
void logErrors() {
super.logErrors();
if (isImportingControlledTestData()) {
super.expectErrors("""
validation failed for id:5002452( HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-empty, mim00-empty, MANAGED_WEBSPACE:mim00, {
"target": "[]"
}
)): ['EMAIL_ALIAS:mim00-empty.config.target' length is expected to be at min 1 but length of [[]] is 0]""",
"""
validation failed for id:5002453( HsHostingAssetRawEntity(EMAIL_ALIAS, mim00-0_entries, mim00-0_entries, MANAGED_WEBSPACE:mim00, {
"target": "[]"
}
)): ['EMAIL_ALIAS:mim00-0_entries.config.target' length is expected to be at min 1 but length of [[]] is 0]"""
);
} else {
super.logErrors();
}
}
private void persistRecursively(final Integer key, final HsBookingItemEntity bi) {
@@ -323,19 +515,21 @@ public class ImportHostingAssets extends ImportOfficeData {
persist(key, HsBookingItemEntityValidatorRegistry.validated(bi));
}
// ============================================================================================
private void persistHostingAssetsOfType(final HsHostingAssetType hsHostingAssetType) {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
hostingAssets.forEach((key, ha) -> {
if (ha.getType() == hsHostingAssetType) {
new HostingAssetEntitySaveProcessor(ha)
.preprocessEntity()
.validateEntity()
.prepareForSave()
.saveUsing(entity -> persist(key, entity))
.validateContext();
context(rbacSuperuser);
if (ha.getType() == hsHostingAssetType) {
new HostingAssetEntitySaveProcessor(em, ha)
.preprocessEntity()
.validateEntityIgnoring("'EMAIL_ALIAS:.*\\.config\\.target' .*")
.prepareForSave()
.saveUsing(entity -> persist(key, entity))
.validateContext();
}
}
}
);
}).assertSuccessful();
}
@@ -346,7 +540,7 @@ public class ImportHostingAssets extends ImportOfficeData {
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var ipNumber = HsHostingAssetEntity.builder()
final var ipNumber = HsHostingAssetRawEntity.builder()
.type(IPV4_NUMBER)
.identifier(rec.getString("inet_addr"))
.caption(rec.getString("description"))
@@ -402,12 +596,17 @@ public class ImportHostingAssets extends ImportOfficeData {
bookingItems.put(PACKET_ID_OFFSET + packet_id, bookingItem);
final var haType = determineHaType(basepacket_code);
logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject().getDebitor().getDefaultPrefix().equals("hsh"))
.as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for " + packet_name)
logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject()
.getDebitor()
.getDefaultPrefix()
.equals("hsh"))
.as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for "
+ packet_name)
.isTrue());
final var asset = HsHostingAssetEntity.builder()
.isLoaded(haType == MANAGED_WEBSPACE) // this turns off identifier validation to accept former default prefixes
final var asset = HsHostingAssetRawEntity.builder()
// this turns off identifier validation to accept former default prefixes
.isLoaded(haType == MANAGED_WEBSPACE)
.type(haType)
.identifier(packet_name)
.bookingItem(bookingItem)
@@ -461,9 +660,9 @@ public class ImportHostingAssets extends ImportOfficeData {
case "DAEMON" -> "Daemons";
case "MULTI" -> "Multi";
case "CPU" -> "CPU";
case "RAM" -> returning("RAM", convert = v -> v/1024);
case "QUOTA" -> returning("SSD", convert = v -> v/1024);
case "STORAGE" -> returning("HDD", convert = v -> v/1024);
case "RAM" -> returning("RAM", convert = v -> v / 1024);
case "QUOTA" -> returning("SSD", convert = v -> v / 1024);
case "STORAGE" -> returning("HDD", convert = v -> v / 1024);
case "TRAFFIC" -> "Traffic";
case "OFFICE" -> returning("Online Office Server", convert = v -> v == 1);
@@ -526,7 +725,7 @@ public class ImportHostingAssets extends ImportOfficeData {
case "SLAPLAT8H" -> "EXT8H";
default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code);
};
if ( ofNullable(asset.getBookingItem().getResources().get(name)).map("BASIC"::equals).orElse(true) ) {
if (ofNullable(asset.getBookingItem().getResources().get(name)).map("BASIC"::equals).orElse(true)) {
asset.getBookingItem().getResources().put(name, slaValue);
}
} else if (name.startsWith("SLA")) {
@@ -537,7 +736,90 @@ public class ImportHostingAssets extends ImportOfficeData {
});
}
<V> V returning(final V value, final Object... assignments) {
private void importUnixUsers(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var unixuser_id = rec.getInteger("unixuser_id");
final var packet_id = rec.getInteger("packet_id");
final var unixUserAsset = HsHostingAssetRawEntity.builder()
.type(UNIX_USER)
.parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id))
.identifier(rec.getString("name"))
.caption(rec.getString("comment"))
.isLoaded(true) // avoid overwriting imported userids with generated ids
.config(new HashMap<>(Map.ofEntries(
entry("shell", rec.getString("shell")),
// entry("homedir", rec.getString("homedir")), do not import, it's calculated
entry("locked", rec.getBoolean("locked")),
entry("userid", rec.getInteger("userid")),
entry("SSD soft quota", rec.getInteger("quota_softlimit")),
entry("SSD hard quota", rec.getInteger("quota_hardlimit")),
entry("HDD soft quota", rec.getInteger("storage_softlimit")),
entry("HDD hard quota", rec.getInteger("storage_hardlimit"))
)))
.build();
// TODO.spec: crop SSD+HDD limits if > booked
if (unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0)
> 1024*unixUserAsset.getContextValue("SSD", Integer.class, 0)) {
unixUserAsset.getConfig().put("SSD hard quota", unixUserAsset.getContextValue("SSD", Integer.class, 0)*1024);
}
if (unixUserAsset.getDirectValue("HDD hard quota", Integer.class, 0)
> 1024*unixUserAsset.getContextValue("HDD", Integer.class, 0)) {
unixUserAsset.getConfig().put("HDD hard quota", unixUserAsset.getContextValue("HDD", Integer.class, 0)*1024);
}
// TODO.spec: does `softlimit<hardlimit?` even make sense? Fix it in this or the other direction?
if (unixUserAsset.getDirectValue("SSD soft quota", Integer.class, 0)
> unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0)) {
unixUserAsset.getConfig().put("SSD soft quota", unixUserAsset.getConfig().get("SSD hard quota"));
}
if (unixUserAsset.getDirectValue("HDD soft quota", Integer.class, 0)
> unixUserAsset.getDirectValue("HDD hard quota", Integer.class, 0)) {
unixUserAsset.getConfig().put("HDD soft quota", unixUserAsset.getConfig().get("HDD hard quota"));
}
// TODO.spec: remove HDD limits if no HDD storage is booked
if (unixUserAsset.getContextValue("HDD", Integer.class, 0) == 0) {
unixUserAsset.getConfig().remove("HDD hard quota");
unixUserAsset.getConfig().remove("HDD soft quota");
}
hostingAssets.put(UNIXUSER_ID_OFFSET + unixuser_id, unixUserAsset);
});
}
private void importEmailAliases(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var unixuser_id = rec.getInteger("emailalias_id");
final var packet_id = rec.getInteger("pac_id");
final var targets = parseCsvLine(rec.getString("target"));
final var unixUserAsset = HsHostingAssetRawEntity.builder()
.type(EMAIL_ALIAS)
.parentAsset(hostingAssets.get(PACKET_ID_OFFSET + packet_id))
.identifier(rec.getString("name"))
.caption(rec.getString("name"))
.config(Map.ofEntries(
entry("target", targets)
))
.build();
hostingAssets.put(EMAILALIAS_ID_OFFSET + unixuser_id, unixUserAsset);
});
}
// ============================================================================================
<V> V returning(
final V value,
@SuppressWarnings("unused") final Object... assignments // DSL-hack: just used for side effects on caller-side
) {
return value;
}
@@ -561,7 +843,7 @@ public class ImportHostingAssets extends ImportOfficeData {
};
}
private static HsHostingAssetEntity ipNumber(final Integer inet_addr_id) {
private static HsHostingAssetRawEntity ipNumber(final Integer inet_addr_id) {
return inet_addr_id != null ? hostingAssets.get(IP_NUMBER_ID_OFFSET + inet_addr_id) : null;
}
@@ -569,7 +851,7 @@ public class ImportHostingAssets extends ImportOfficeData {
return hive_id != null ? hives.get(HIVE_ID_OFFSET + hive_id) : null;
}
private static HsHostingAssetEntity pac(final Integer packet_id) {
private static HsHostingAssetRawEntity pac(final Integer packet_id) {
return packet_id != null ? hostingAssets.get(PACKET_ID_OFFSET + packet_id) : null;
}
@@ -582,7 +864,11 @@ public class ImportHostingAssets extends ImportOfficeData {
.filter(hae -> hae.getValue().getType() == t)
.limit(maxCount)
)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, ImportHostingAssets::uniqueKeys, TreeMap::new)));
}
protected static <V> V uniqueKeys(final V v1, final V v2) {
throw new RuntimeException(String.format("Duplicate key for values %s and %s", v1, v2));
}
private String firstOfEachType(

View File

@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import jakarta.persistence.EntityManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -19,6 +20,7 @@ class PasswordPropertyUnitTest {
private final ValidatableProperty<PasswordProperty, String> passwordProp =
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(LINUX_SHA512).writeOnly();
private final List<String> violations = new ArrayList<>();
private EntityManager em = null; // not actually needed in these test cases
@ParameterizedTest
@ValueSource(strings = {
@@ -99,7 +101,12 @@ class PasswordPropertyUnitTest {
void shouldComputeHash() {
// when
final var result = passwordProp.compute(new PropertiesProvider() {
final var result = passwordProp.compute(em, new PropertiesProvider() {
@Override
public boolean isLoaded() {
return false;
}
@Override
public Map<String, Object> directProps() {

View File

@@ -0,0 +1,12 @@
emailalias_id;pac_id;name;target
2403;1094;lug00;michael.mellis@example.com
2405;1094;lug00-wla-listar;|/home/pacs/lug00/users/in/mailinglist/listar
2429;1112;mim00;mim12-mi@mim12.hostsharing.net
2431;1112;mim00-abruf;michael.mellis@hostsharing.net
2449;1112;mim00-hhfx;"mim00-hhfx,""|/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l"""
2451;1112;mim00-hhfx-l;:include:/home/pacs/mim00/etc/hhfx.list
2452;1112;mim00-empty;
2453;1112;mim00-0_entries;""
2454;1112;mim00-dev.null; /dev/null
2455;1112;mim00-1_with_space;" ""|/home/pacs/mim00/install/corpslistar/listar"""
2456;1112;mim00-1_with_single_quotes;'|/home/pacs/rir00/mailinglist/ecartis -r kybs06-intern'
1 emailalias_id pac_id name target
2 2403 1094 lug00 michael.mellis@example.com
3 2405 1094 lug00-wla-listar |/home/pacs/lug00/users/in/mailinglist/listar
4 2429 1112 mim00 mim12-mi@mim12.hostsharing.net
5 2431 1112 mim00-abruf michael.mellis@hostsharing.net
6 2449 1112 mim00-hhfx mim00-hhfx,"|/usr/bin/formail -I 'Reply-To: hamburger-fx@example.net' | /usr/lib/sendmail mim00-hhfx-l"
7 2451 1112 mim00-hhfx-l :include:/home/pacs/mim00/etc/hhfx.list
8 2452 1112 mim00-empty
9 2453 1112 mim00-0_entries
10 2454 1112 mim00-dev.null /dev/null
11 2455 1112 mim00-1_with_space "|/home/pacs/mim00/install/corpslistar/listar"
12 2456 1112 mim00-1_with_single_quotes '|/home/pacs/rir00/mailinglist/ecartis -r kybs06-intern'

View File

@@ -0,0 +1,19 @@
unixuser_id;name;comment;shell;homedir;locked;packet_id;userid;quota_softlimit;quota_hardlimit;storage_softlimit;storage_hardlimit
100824;hsh00;Hostsharing Paket;/bin/bash;/home/pacs/hsh00;0;630;10000;0;0;0;0
5803;lug00;LUGs;/bin/bash;/home/pacs/lug00;0;1094;102090;0;0;0;0
5805;lug00-wla.1;Paul Klemm;/bin/bash;/home/pacs/lug00/users/deaf;0;1094;102091;4;0;0;0
5809;lug00-wla.2;Walter Müller;/bin/bash;/home/pacs/lug00/users/marl;0;1094;102093;4;8;0;0
5811;lug00-ola.a;LUG OLA - POP a;/usr/bin/passwd;/home/pacs/lug00/users/marl.a;1;1094;102094;0;0;0;0
5813;lug00-ola.b;LUG OLA - POP b;/usr/bin/passwd;/home/pacs/lug00/users/marl.b;1;1094;102095;0;0;0;0
5835;lug00-test;Test;/usr/bin/passwd;/home/pacs/lug00/users/test;0;1094;102106;2000000;4000000;20;0
100705;hsh00-mim;Michael Mellis;/bin/false;/home/pacs/hsh00/users/mi;0;630;10003;0;0;0;0
5964;mim00;Michael Mellis;/bin/bash;/home/pacs/mim00;0;1112;102147;0;0;0;0
5966;mim00-1981;Jahrgangstreffen 1981;/bin/bash;/home/pacs/mim00/users/1981;0;1112;102148;128;256;0;0
5990;mim00-mail;Mailbox;/bin/bash;/home/pacs/mim00/users/mail;0;1112;102160;0;0;0;0
167846;hsh00-dph;hsh00-uph;/bin/false;/home/pacs/hsh00/users/uph;0;630;110568;0;0;0;0
169546;dph00;Reinhard Wiese;/bin/bash;/home/pacs/dph00;0;19959;110593;0;0;0;0
169596;dph00-uph;Domain admin;/bin/bash;/home/pacs/dph00/users/uph;0;19959;110594;0;0;0;0
1 unixuser_id name comment shell homedir locked packet_id userid quota_softlimit quota_hardlimit storage_softlimit storage_hardlimit
2 100824 hsh00 Hostsharing Paket /bin/bash /home/pacs/hsh00 0 630 10000 0 0 0 0
3 5803 lug00 LUGs /bin/bash /home/pacs/lug00 0 1094 102090 0 0 0 0
4 5805 lug00-wla.1 Paul Klemm /bin/bash /home/pacs/lug00/users/deaf 0 1094 102091 4 0 0 0
5 5809 lug00-wla.2 Walter Müller /bin/bash /home/pacs/lug00/users/marl 0 1094 102093 4 8 0 0
6 5811 lug00-ola.a LUG OLA - POP a /usr/bin/passwd /home/pacs/lug00/users/marl.a 1 1094 102094 0 0 0 0
7 5813 lug00-ola.b LUG OLA - POP b /usr/bin/passwd /home/pacs/lug00/users/marl.b 1 1094 102095 0 0 0 0
8 5835 lug00-test Test /usr/bin/passwd /home/pacs/lug00/users/test 0 1094 102106 2000000 4000000 20 0
9 100705 hsh00-mim Michael Mellis /bin/false /home/pacs/hsh00/users/mi 0 630 10003 0 0 0 0
10 5964 mim00 Michael Mellis /bin/bash /home/pacs/mim00 0 1112 102147 0 0 0 0
11 5966 mim00-1981 Jahrgangstreffen 1981 /bin/bash /home/pacs/mim00/users/1981 0 1112 102148 128 256 0 0
12 5990 mim00-mail Mailbox /bin/bash /home/pacs/mim00/users/mail 0 1112 102160 0 0 0 0
13 167846 hsh00-dph hsh00-uph /bin/false /home/pacs/hsh00/users/uph 0 630 110568 0 0 0 0
14 169546 dph00 Reinhard Wiese /bin/bash /home/pacs/dph00 0 19959 110593 0 0 0 0
15 169596 dph00-uph Domain admin /bin/bash /home/pacs/dph00/users/uph 0 19959 110594 0 0 0 0