allow _ in unixuser names etc.

This commit is contained in:
Michael Hoennig 2024-07-30 10:02:01 +02:00
parent 5d438f214f
commit 7193772f98
10 changed files with 29 additions and 25 deletions

View File

@ -18,7 +18,7 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator { class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator {
// according to RFC 1035 (section 5) and RFC 1034 // according to RFC 1035 (section 5) and RFC 1034
static final String RR_REGEX_NAME = "([a-z0-9\\.-]+|@)\\s+"; static final String RR_REGEX_NAME = "([a-z0-9\\._-]+|@)\\s+";
static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*"; static final String RR_REGEX_TTL = "(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*";
static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet static final String RR_REGEX_IN = "IN\\s+"; // record class IN for Internet
static final String RR_RECORD_TYPE = "[A-Z]+\\s+"; static final String RR_RECORD_TYPE = "[A-Z]+\\s+";

View File

@ -11,7 +11,7 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator { class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.-]*)?$"; // also accepts legacy pac-names private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$"; // also accepts legacy pac-names
private static final String EMAIL_ADDRESS_LOCAL_PART_REGEX = "[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+"; // RFC 5322 private static final String EMAIL_ADDRESS_LOCAL_PART_REGEX = "[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+"; // RFC 5322
private static final String EMAIL_ADDRESS_DOMAIN_PART_REGEX = "[a-zA-Z0-9.-]+"; private static final String EMAIL_ADDRESS_DOMAIN_PART_REGEX = "[a-zA-Z0-9.-]+";
private static final String EMAIL_ADDRESS_FULL_REGEX = "^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "@" + EMAIL_ADDRESS_DOMAIN_PART_REGEX + "$"; private static final String EMAIL_ADDRESS_FULL_REGEX = "^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "@" + EMAIL_ADDRESS_DOMAIN_PART_REGEX + "$";

View File

@ -10,7 +10,7 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator { class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator {
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\.-]*)?$"; // also accepts legacy pac-names private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$"; // also accepts legacy pac-names
private static final String EMAIL_ADDRESS_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"; // RFC 5322 private static final String EMAIL_ADDRESS_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"; // RFC 5322
private static final String INCLUDE_REGEX = "^:include:/.*$"; private static final String INCLUDE_REGEX = "^:include:/.*$";
private static final String PIPE_REGEX = "^|.*$"; private static final String PIPE_REGEX = "^|.*$";
@ -28,6 +28,6 @@ class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator {
@Override @Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier(); final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9][a-z0-9\\.-]*$"); return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9][a-z0-9\\._-]*$");
} }
} }

View File

@ -11,7 +11,7 @@ class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator
super( super(
MANAGED_WEBSPACE, MANAGED_WEBSPACE,
AlarmContact.isOptional(), AlarmContact.isOptional(),
NO_EXTRA_PROPERTIES); NO_EXTRA_PROPERTIES); // TODO.impl: groupid missing, should be equal to main user
} }
@Override @Override

View File

@ -30,8 +30,8 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
integerProperty("SSD soft quota").unit("MB").maxFrom("SSD hard quota").optional(), integerProperty("SSD soft quota").unit("MB").maxFrom("SSD hard quota").optional(),
integerProperty("HDD hard quota").unit("MB").maxFrom("HDD").withFactor(1024).optional(), integerProperty("HDD hard quota").unit("MB").maxFrom("HDD").withFactor(1024).optional(),
integerProperty("HDD soft quota").unit("MB").maxFrom("HDD hard quota").optional(), integerProperty("HDD soft quota").unit("MB").maxFrom("HDD hard quota").optional(),
enumerationProperty("shell") stringProperty("shell")
.values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd") .provided("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd")
.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(),
@ -42,7 +42,7 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
@Override @Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier(); final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9\\.-]+$"); return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9\\._-]+$");
} }
private static String computeHomedir(final EntityManager em, final PropertiesProvider propertiesProvider) { private static String computeHomedir(final EntityManager em, final PropertiesProvider propertiesProvider) {
@ -53,6 +53,8 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
} }
private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) { private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) {
return Math.toIntExact((Long) em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')").getSingleResult()); final Object result = em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')", Integer.class)
.getSingleResult();
return (Integer) result;
} }
} }

View File

@ -68,7 +68,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
"{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}", "{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}", "{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}", "{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}",
"{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}" "{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\._-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\._-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}"
); );
} }
@ -166,8 +166,8 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_DNS_SETUP:example.org|DNS.config.TTL' is expected to be of type Integer, but is of type String", "'DOMAIN_DNS_SETUP:example.org|DNS.config.TTL' is expected to be of type Integer, but is of type String",
"'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any", "'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [([a-z0-9\\._-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\._-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any",
"'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but 'www BAD1 Record-Class missing / not enough columns' does not match any"); "'DOMAIN_DNS_SETUP:example.org|DNS.config.user-RR' is expected to match any of [([a-z0-9\\._-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\._-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but 'www BAD1 Record-Class missing / not enough columns' does not match any");
} }
@Test @Test

View File

@ -39,7 +39,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=string, propertyName=local-part, matchesRegEx=[^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+$], required=true}", "{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=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 @Test
@ -73,7 +73,7 @@ class HsEMailAddressHostingAssetValidatorUnitTest {
assertThat(result).containsExactlyInAnyOrder( 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.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.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 @Test

View File

@ -22,7 +22,7 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( 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:/.*$, ^|.*$], maxLength=320}, required=true, minLength=1}");
} }
@Test @Test

View File

@ -70,7 +70,7 @@ public class CsvDataImport extends ContextBasedTest {
@MockBean @MockBean
HttpServletRequest request; 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 { public List<String[]> readAllLines(Reader reader) throws Exception {
@ -136,7 +136,7 @@ public class CsvDataImport extends ContextBasedTest {
try { try {
final var asString = entity.toString(); final var asString = entity.toString();
if ( asString.contains("'null null, null'") || asString.equals("person()")) { if ( asString.contains("'null null, null'") || asString.equals("person()")) {
System.err.println("skipping to persist empty record-id " + id + " #" + entity.hashCode() + ": " + entity); errors.add("skipping to persist empty record-id " + id + " #" + entity.hashCode() + ": " + entity);
return entity; return entity;
} }
//System.out.println("persisting #" + entity.hashCode() + ": " + entity); //System.out.println("persisting #" + entity.hashCode() + ": " + entity);
@ -145,8 +145,8 @@ public class CsvDataImport extends ContextBasedTest {
// em.flush(); // makes it slow, but produces better error messages // em.flush(); // makes it slow, but produces better error messages
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid()); // System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
} catch (Exception exc) { } catch (Exception exc) {
System.err.println("failed to persist #" + entity.hashCode() + ": " + entity); errors.add("failed to persist #" + entity.hashCode() + ": " + entity);
System.err.println(exc); errors.add(exc.toString());
} }
return entity; return entity;
} }
@ -225,7 +225,7 @@ public class CsvDataImport extends ContextBasedTest {
try { try {
assertion.run(); assertion.run();
} catch (final AssertionError exc) { } catch (final AssertionError exc) {
errors.add(exc); errors.add(exc.toString());
} }
} }

View File

@ -97,6 +97,7 @@ public class ImportHostingAssets extends ImportOfficeData {
static final Integer HIVE_ID_OFFSET = 2000000; static final Integer HIVE_ID_OFFSET = 2000000;
static final Integer PACKET_ID_OFFSET = 3000000; static final Integer PACKET_ID_OFFSET = 3000000;
static final Integer UNIXUSER_ID_OFFSET = 4000000; 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<HsHostingAssetEntity> serverRef) {}
@ -342,7 +343,7 @@ public class ImportHostingAssets extends ImportOfficeData {
try { try {
HsBookingItemEntityValidatorRegistry.validated(bi); HsBookingItemEntityValidatorRegistry.validated(bi);
} catch (final Exception exc) { } 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());
} }
}); });
} }
@ -356,7 +357,7 @@ public class ImportHostingAssets extends ImportOfficeData {
.preprocessEntity() .preprocessEntity()
.validateEntity(); .validateEntity();
} catch (final Exception exc) { } 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());
} }
}); });
} }
@ -388,12 +389,14 @@ public class ImportHostingAssets extends ImportOfficeData {
persistHostingAssetsOfType(EMAIL_ALIAS); persistHostingAssetsOfType(EMAIL_ALIAS);
} }
@Test @Test
@Order(19010) @Order(19010)
void verifyPersistedUnixUsersWithUserId() { void verifyPersistedUnixUsersWithUserId() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values // no contacts yet => mostly null value
// FIXME: keep original userids
assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace(""" assertThat(firstOfEachType(15, UNIX_USER)).isEqualToIgnoringWhitespace("""
{ {
4005803=HsHostingAssetEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000000}), 4005803=HsHostingAssetEntity(UNIX_USER, lug00, LUGs, MANAGED_WEBSPACE:lug00, { "HDD hard quota": 0, "HDD soft quota": 0, "SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "password": null, "shell": "/bin/bash", "userid": 100000000}),
@ -414,7 +417,6 @@ public class ImportHostingAssets extends ImportOfficeData {
"""); """);
} }
// ============================================================================================ // ============================================================================================
@Test @Test
@ -697,7 +699,7 @@ public class ImportHostingAssets extends ImportOfficeData {
entry("target", targets) entry("target", targets)
)) ))
.build(); .build();
hostingAssets.put(UNIXUSER_ID_OFFSET + unixuser_id, unixUserAsset); hostingAssets.put(EMAILALIAS_ID_OFFSET + unixuser_id, unixUserAsset);
}); });
} }