From 7cb663cc1ce2fa5cf02e717b49fa6f76b968d792 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Tue, 17 Mar 2020 15:09:23 -0400 Subject: [PATCH] reserve account names as network names, better name validation for new networks --- .../bubble/dao/cloud/BubbleNetworkDAO.java | 8 +++- .../java/bubble/model/account/Account.java | 6 ++- .../java/bubble/model/bill/AccountPlan.java | 4 +- .../bubble/model/cloud/BubbleNetwork.java | 38 ++++++++++++++----- .../resources/bill/AccountPlansResource.java | 4 +- .../post_auth/ResourceMessages.properties | 11 ------ .../pre_auth/ResourceMessages.properties | 12 ++++++ 7 files changed, 55 insertions(+), 28 deletions(-) diff --git a/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java b/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java index 0b6c4761..b6fac0cf 100644 --- a/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java +++ b/bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java @@ -4,6 +4,7 @@ */ package bubble.dao.cloud; +import bubble.dao.account.AccountDAO; import bubble.dao.account.AccountOwnedEntityDAO; import bubble.dao.bill.AccountPlanDAO; import bubble.model.bill.AccountPlan; @@ -29,6 +30,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @Repository @Slf4j public class BubbleNetworkDAO extends AccountOwnedEntityDAO { + @Autowired private AccountDAO accountDAO; @Autowired private BubbleDomainDAO domainDAO; @Autowired private NetworkService networkService; @Autowired private BubbleBackupDAO backupDAO; @@ -40,7 +42,7 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO { @Override public Object preCreate(BubbleNetwork network) { if (!network.hasForkHost()) { - final ValidationResult errors = validateHostname(network); + final ValidationResult errors = validateHostname(network, accountDAO, this); if (errors.isInvalid()) throw invalidEx(errors); } final AnsibleInstallType installType = network.hasForkHost() && configuration.isSageLauncher() @@ -85,6 +87,10 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO { return findByUniqueFields("name", name, "domainName", domainName); } + public BubbleNetwork findByNameAndDomainUuid(String name, String domainUuid) { + return findByUniqueFields("name", name, "domain", domainUuid); + } + @Override public void delete(String uuid) { delete(uuid, false); } @Override public void forceDelete(String uuid) { delete(uuid, true); } diff --git a/bubble-server/src/main/java/bubble/model/account/Account.java b/bubble-server/src/main/java/bubble/model/account/Account.java index adb9d018..f95b87ed 100644 --- a/bubble-server/src/main/java/bubble/model/account/Account.java +++ b/bubble-server/src/main/java/bubble/model/account/Account.java @@ -116,9 +116,11 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci private static final List RESERVED_NAMES = Arrays.asList( "root", "postmaster", "hostmaster", "webmaster", - "ftp", "www", "www-data", "postgres", "ipfs", + "dns", "dnscrypt", "dnscrypt-proxy", "ftp", "www", "www-data", "postgres", "ipfs", "redis", "nginx", "mitmproxy", "mitmdump", "algo", "algovpn"); - public boolean hasReservedName () { return hasName() && RESERVED_NAMES.contains(getName()); } + public boolean hasReservedName () { return hasName() && isReservedName(getName()); } + + public static boolean isReservedName(String name) { return RESERVED_NAMES.contains(name); } // make this updatable if we ever want accounts to be able to change parents // there might be a lot more involved in that action though (read-only parent objects that will no longer be visible, must be copied in?) diff --git a/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java b/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java index 46cfcd60..d0a6e273 100644 --- a/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java +++ b/bubble-server/src/main/java/bubble/model/bill/AccountPlan.java @@ -6,7 +6,7 @@ package bubble.model.bill; import bubble.model.account.Account; import bubble.model.account.AccountSshKey; -import bubble.model.account.HasAccount; +import bubble.model.account.HasNetwork; import bubble.model.cloud.BubbleDomain; import bubble.model.cloud.BubbleNetwork; import bubble.model.cloud.CloudService; @@ -40,7 +40,7 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; @ECIndex(unique=true, of={"account", "network"}), @ECIndex(unique=true, of={"plan", "network"}) }) -public class AccountPlan extends IdentifiableBase implements HasAccount { +public class AccountPlan extends IdentifiableBase implements HasNetwork { public static final String[] UPDATE_FIELDS = {"description", "paymentMethod", "paymentMethodObject"}; diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java index 4089545a..0167b73a 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java @@ -5,6 +5,8 @@ package bubble.model.cloud; import bubble.cloud.compute.ComputeNodeSizeType; +import bubble.dao.account.AccountDAO; +import bubble.dao.cloud.BubbleNetworkDAO; import bubble.model.BubbleTags; import bubble.model.HasBubbleTags; import bubble.model.account.Account; @@ -20,7 +22,6 @@ import lombok.experimental.Accessors; import org.cobbzilla.util.collection.ArrayUtil; import org.cobbzilla.wizard.model.Identifiable; import org.cobbzilla.wizard.model.IdentifiableBase; -import org.cobbzilla.wizard.model.NamedEntity; import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; import org.cobbzilla.wizard.model.entityconfig.annotations.*; import org.cobbzilla.wizard.validation.HasValue; @@ -179,19 +180,36 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu return die("hostFromFqdn("+fqdn+"): expected suffix ."+getNetworkDomain()); } - public static ValidationResult validateHostname(NamedEntity request) { - return validateHostname(request, new ValidationResult()); + public static ValidationResult validateHostname(HasNetwork request, + AccountDAO accountDAO, + BubbleNetworkDAO networkDAO) { + return validateHostname(request, new ValidationResult(), accountDAO, networkDAO); } - public static ValidationResult validateHostname(NamedEntity request, ValidationResult errors) { + public static ValidationResult validateHostname(HasNetwork request, + ValidationResult errors, + AccountDAO accountDAO, + BubbleNetworkDAO networkDAO) { if (!request.hasName()) { errors.addViolation("err.name.required"); - } else if (!validateRegexMatches(HOST_PART_PATTERN, request.getName())) { - errors.addViolation("err.name.invalid"); - } else if (request.getName().length() > NETWORK_NAME_MAXLEN) { - errors.addViolation("err.name.length"); - } else if (request.getName().length() < NETWORK_NAME_MINLEN) { - errors.addViolation("err.name.tooShort"); + } else { + final String name = request.getName(); + if (!validateRegexMatches(HOST_PART_PATTERN, name)) { + errors.addViolation("err.name.invalid"); + } else if (Account.isReservedName(name)) { + errors.addViolation("err.name.reserved"); + } else if (name.length() > NETWORK_NAME_MAXLEN) { + errors.addViolation("err.name.length"); + } else if (name.length() < NETWORK_NAME_MINLEN) { + errors.addViolation("err.name.tooShort"); + } else if (networkDAO.findByNameAndDomainUuid(name, request.getDomain()) != null) { + errors.addViolation("err.name.alreadyInUse"); + } else { + final Account acct = accountDAO.findByName(name); + if (acct != null && !acct.getUuid().equals(request.getAccount())) { + errors.addViolation("err.name.reservedForAccount"); + } + } } return errors; } diff --git a/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java b/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java index 50736037..df3e89ff 100644 --- a/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java +++ b/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java @@ -146,11 +146,11 @@ public class AccountPlansResource extends AccountOwnedResource(refresh page if this gets stuck) button_label_customize=Customize @@ -597,19 +594,11 @@ err.mitm.changeInProgress=A change to MITM is already in progress err.mitm.errorReadingControlFile=Error reading MITM control file err.mitm.errorWritingControlFile=Error writing MITM control file err.mitm.notInstalled=MITM component is not installed -err.name.invalid=Name is invalid -err.name.networkNameAlreadyExists=Name is already in use -err.name.regexFailed=Name must start with a letter and can only contain letters, numbers, hyphens, periods and underscores err.node.name.alreadyExists=A node already exists with the same FQDN err.node.cannotStopLocalNode=Cannot stop local Bubble err.nodeOperationTimeout.required=Node operation timeout is required err.nodeOperationTimeout.tooLong=Node operation timeout cannot be longer than 3 days err.nodeOperationTimeout.tooShort=Node operation timeout cannot be shorter than 1 minute -err.name.length=Name is too long -err.name.tooShort=Name is too short -err.name.mismatch=Name mismatch -err.name.reserved=Name is reserved -err.name.alreadyInUse=Name is already in use err.netlocation.invalid=Must specify both cloud and region, or neither err.network.alreadyStarted=Network is already started err.network.exists=A plan already exists for this network diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties index c81077ee..bd3befff 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties @@ -165,9 +165,17 @@ err.geoLocateService.notFound=GeoLocation service not found err.app.notFound=App not found err.name.required=Name is required err.name.notFound=Account not found +err.name.length=Name is too long err.name.tooShort=Name must be at least 4 characters err.name.tooLong=Name cannot be longer than 100 characters err.name.planMaxAccountLimit=No more accounts can be created on this plan. Upgrade your plan or delete some accounts. +err.name.alreadyInUse=Name is already in use +err.name.reservedForAccount=Name is already in use +err.name.reserved=Name is reserved +err.name.invalid=Name is invalid +err.name.networkNameAlreadyExists=Name is already in use +err.name.regexFailed=Name must start with a letter and can only contain letters, numbers, hyphens, periods and underscores +err.name.mismatch=Name mismatch err.password.required=Password is required err.password.tooShort=Password must be at least 8 characters err.password.invalid=Password must contain at least one letter, one number, and one special character @@ -266,6 +274,10 @@ message_login_agreeToTerms=By logging in, you agree to the