Browse Source

replace all hardcoded refs to root as admin username. admin username is set during activation.

tags/v1.4.17
Jonathan Cobb 4 years ago
parent
commit
96a8a8ebbf
17 changed files with 112 additions and 26 deletions
  1. +3
    -2
      bubble-server/src/main/java/bubble/dao/account/AccountDAO.java
  2. +1
    -1
      bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java
  3. +0
    -1
      bubble-server/src/main/java/bubble/model/account/Account.java
  4. +5
    -1
      bubble-server/src/main/java/bubble/model/bill/AccountPlan.java
  5. +5
    -1
      bubble-server/src/main/java/bubble/model/boot/ActivationRequest.java
  6. +3
    -0
      bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java
  7. +8
    -1
      bubble-server/src/main/java/bubble/model/cloud/LaunchType.java
  8. +5
    -0
      bubble-server/src/main/java/bubble/resources/account/AuthResource.java
  9. +14
    -2
      bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java
  10. +2
    -2
      bubble-server/src/main/java/bubble/resources/cloud/ComputePackerResource.java
  11. +16
    -0
      bubble-server/src/main/java/bubble/service/boot/ActivationService.java
  12. +5
    -5
      bubble-server/src/main/java/bubble/service/boot/StandardSelfNodeService.java
  13. +24
    -4
      bubble-server/src/main/java/bubble/service/dbfilter/EntityIterator.java
  14. +1
    -1
      bubble-server/src/main/resources/messages
  15. +1
    -1
      bubble-web
  16. +18
    -3
      config/activation.json
  17. +1
    -1
      utils/cobbzilla-utils

+ 3
- 2
bubble-server/src/main/java/bubble/dao/account/AccountDAO.java View File

@@ -164,7 +164,7 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc


// create an uninitialized device for the account, but only if this is a regular node network // create an uninitialized device for the account, but only if this is a regular node network
// sage networks do not allow devices, they launch and manage other regular node networks // sage networks do not allow devices, they launch and manage other regular node networks
if (!account.isRoot() && !configuration.isSage()) {
if (!isFirstAdmin(account) && !configuration.isSage()) {
deviceDAO.ensureAllSpareDevices(account.getUuid(), configuration.getThisNetwork().getUuid()); deviceDAO.ensureAllSpareDevices(account.getUuid(), configuration.getThisNetwork().getUuid());
deviceDAO.refreshVpnUsers(); deviceDAO.refreshVpnUsers();
} }
@@ -475,7 +475,8 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc
private final Refreshable<Account> firstAdmin = new Refreshable<>("firstAdmin", FIRST_ADMIN_CACHE_MILLIS, this::findFirstAdmin); private final Refreshable<Account> firstAdmin = new Refreshable<>("firstAdmin", FIRST_ADMIN_CACHE_MILLIS, this::findFirstAdmin);
public Account getFirstAdmin() { return firstAdmin.get(); } public Account getFirstAdmin() { return firstAdmin.get(); }


public boolean isFirstAdmin(Account account) { return getFirstAdmin().getUuid().equals(account.getUuid()); }
public boolean isFirstAdmin(Account account) { return isFirstAdmin(account.getUuid()); }
public boolean isFirstAdmin(String accountUuid) { return getFirstAdmin().getUuid().equals(accountUuid); }


public Account findFirstAdmin() { public Account findFirstAdmin() {
final List<Account> admins = findByField("admin", true); final List<Account> admins = findByField("admin", true);


+ 1
- 1
bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java View File

@@ -81,7 +81,7 @@ public class DeviceDAO extends AccountOwnedEntityDAO<Device> {
var uninitializedDevices = findByAccountAndUninitialized(accountUuid); var uninitializedDevices = findByAccountAndUninitialized(accountUuid);


if (uninitializedDevices.size() <= SPARE_DEVICES_PER_ACCOUNT_THRESHOLD if (uninitializedDevices.size() <= SPARE_DEVICES_PER_ACCOUNT_THRESHOLD
&& !configuration.getBean(AccountDAO.class).findByUuid(accountUuid).isRoot()) {
&& !configuration.getBean(AccountDAO.class).isFirstAdmin(accountUuid)) {
if (ensureAllSpareDevices(accountUuid, device.getNetwork())) refreshVpnUsers(); if (ensureAllSpareDevices(accountUuid, device.getNetwork())) refreshVpnUsers();
} }




+ 0
- 1
bubble-server/src/main/java/bubble/model/account/Account.java View File

@@ -112,7 +112,6 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci


@Override @Transient public String getName() { return getEmail(); } @Override @Transient public String getName() { return getEmail(); }
public Account setName(String n) { return setEmail(n); } public Account setName(String n) { return setEmail(n); }
@Transient @JsonIgnore public boolean isRoot() { return getName().equals(ROOT_USERNAME); }


// make this updatable if we ever want accounts to be able to change parents // 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?) // 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?)


+ 5
- 1
bubble-server/src/main/java/bubble/model/bill/AccountPlan.java View File

@@ -166,6 +166,9 @@ public class AccountPlan extends IdentifiableBase implements HasNetwork {
@Transient @Getter @Setter private transient String forkHost = null; @Transient @Getter @Setter private transient String forkHost = null;
public boolean hasForkHost () { return !empty(forkHost); } public boolean hasForkHost () { return !empty(forkHost); }


@Transient @Getter @Setter private transient String adminEmail = null;
public boolean hasAdminEmail() { return !empty(adminEmail); }

@Transient @Getter @Setter private transient Boolean syncAccount = null; @Transient @Getter @Setter private transient Boolean syncAccount = null;
public boolean syncAccount() { return syncAccount == null || syncAccount; } public boolean syncAccount() { return syncAccount == null || syncAccount; }


@@ -200,7 +203,8 @@ public class AccountPlan extends IdentifiableBase implements HasNetwork {
.setComputeSizeType(plan.getComputeSizeType()) .setComputeSizeType(plan.getComputeSizeType())
.setStorage(storage.getUuid()) .setStorage(storage.getUuid())
.setLaunchType(hasForkHost() && hasLaunchType() ? getLaunchType() : LaunchType.node) .setLaunchType(hasForkHost() && hasLaunchType() ? getLaunchType() : LaunchType.node)
.setForkHost(hasForkHost() ? getForkHost() : null);
.setForkHost(hasForkHost() ? getForkHost() : null)
.setAdminEmail(hasAdminEmail() ? getAdminEmail() : null);
} }


} }

+ 5
- 1
bubble-server/src/main/java/bubble/model/boot/ActivationRequest.java View File

@@ -23,7 +23,8 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
public class ActivationRequest { public class ActivationRequest {


@HasValue(message="err.email.required") @HasValue(message="err.email.required")
@Getter @Setter private String email;
@Getter private String email;
public ActivationRequest setEmail(String e) { this.email = empty(e) ? e : e.trim(); return this; }
public boolean hasEmail() { return !empty(email); } public boolean hasEmail() { return !empty(email); }


public String getName() { return getEmail(); } public String getName() { return getEmail(); }
@@ -55,6 +56,9 @@ public class ActivationRequest {
@Getter @Setter private Boolean skipTests = false; @Getter @Setter private Boolean skipTests = false;
public boolean skipTests () { return bool(skipTests); }; public boolean skipTests () { return bool(skipTests); };


@Getter @Setter private Boolean skipPacker = false;
public boolean skipPacker () { return bool(skipPacker); };

@Getter @Setter private AccountSshKey sshKey; @Getter @Setter private AccountSshKey sshKey;
public boolean hasSshKey () { return sshKey != null; } public boolean hasSshKey () { return sshKey != null; }




+ 3
- 0
bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java View File

@@ -208,6 +208,9 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu
public boolean hasForkHost () { return !empty(forkHost); } public boolean hasForkHost () { return !empty(forkHost); }
public boolean fork() { return hasForkHost(); } public boolean fork() { return hasForkHost(); }


@Transient @Getter @Setter private transient String adminEmail;
public boolean hasAdminEmail () { return !empty(adminEmail); }

@ECField(index=190) @Column(length=20, updatable=false) @ECField(index=190) @Column(length=20, updatable=false)
@Enumerated(EnumType.STRING) @Getter @Setter private LaunchType launchType = null; @Enumerated(EnumType.STRING) @Getter @Setter private LaunchType launchType = null;
public boolean hasLaunchType () { return launchType != null; } public boolean hasLaunchType () { return launchType != null; }


+ 8
- 1
bubble-server/src/main/java/bubble/model/cloud/LaunchType.java View File

@@ -5,13 +5,20 @@
package bubble.model.cloud; package bubble.model.cloud;


import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;


import static bubble.ApiConstants.enumFromString; import static bubble.ApiConstants.enumFromString;


@AllArgsConstructor
public enum LaunchType { public enum LaunchType {


node, fork_node, fork_sage;
node (false),
fork_node (true),
fork_sage (true);


@JsonCreator public static LaunchType fromString(String v) { return enumFromString(LaunchType.class, v); } @JsonCreator public static LaunchType fromString(String v) { return enumFromString(LaunchType.class, v); }


private final boolean fork;
public boolean fork () { return fork; }

} }

+ 5
- 0
bubble-server/src/main/java/bubble/resources/account/AuthResource.java View File

@@ -166,6 +166,11 @@ public class AuthResource {
if (accountDAO.activated()) { if (accountDAO.activated()) {
return invalid("err.activation.alreadyDone", "activation has already been done"); return invalid("err.activation.alreadyDone", "activation has already been done");
} }

if (!request.hasEmail()) return invalid("err.email.required", "email is required");
if (request.getEmail().contains("{{") && request.getEmail().contains("}}")) {
request.setEmail(configuration.applyHandlebars(request.getEmail()).trim());
}
if (!request.hasEmail()) return invalid("err.email.required", "email is required"); if (!request.hasEmail()) return invalid("err.email.required", "email is required");


if (!request.hasPassword()) return invalid("err.password.required", "password is required"); if (!request.hasPassword()) return invalid("err.password.required", "password is required");


+ 14
- 2
bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java View File

@@ -41,10 +41,10 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;


import static bubble.ApiConstants.*; import static bubble.ApiConstants.*;
import static bubble.model.account.Account.ROOT_EMAIL;
import static bubble.model.cloud.BubbleNetwork.validateHostname; import static bubble.model.cloud.BubbleNetwork.validateHostname;
import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.string.ValidationRegexes.HOST_PATTERN;
import static org.cobbzilla.util.string.ValidationRegexes.validateRegexMatches;
import static org.cobbzilla.util.string.ValidationRegexes.*;
import static org.cobbzilla.wizard.model.NamedEntity.NAME_MAXLEN; import static org.cobbzilla.wizard.model.NamedEntity.NAME_MAXLEN;
import static org.cobbzilla.wizard.resources.ResourceUtil.*; import static org.cobbzilla.wizard.resources.ResourceUtil.*;


@@ -167,6 +167,18 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
validateName(request, errors); validateName(request, errors);
} }
} }
final String adminEmail = request.getAdminEmail();
if (request.hasLaunchType() && request.getLaunchType().fork() && caller.admin()) {
if (empty(adminEmail) && caller.getEmail().equals(ROOT_EMAIL)) {
errors.addViolation("err.adminEmail.required");
} else if (!empty(adminEmail) && !validateRegexMatches(EMAIL_PATTERN, adminEmail)) {
errors.addViolation("err.adminEmail.invalid");
}
} else {
if (!empty(adminEmail)) {
errors.addViolation("err.adminEmail.cannotSet");
}
}
} else { } else {
if (!request.hasNickname()) { if (!request.hasNickname()) {
if (!request.hasName()) request.setName(newNetworkName()); if (!request.hasName()) request.setName(newNetworkName());


+ 2
- 2
bubble-server/src/main/java/bubble/resources/cloud/ComputePackerResource.java View File

@@ -28,8 +28,8 @@ public class ComputePackerResource {
@Autowired private BubbleConfiguration configuration; @Autowired private BubbleConfiguration configuration;
@Autowired private PackerService packer; @Autowired private PackerService packer;


private Account account;
private CloudService cloud;
private final Account account;
private final CloudService cloud;


public ComputePackerResource (Account account, CloudService cloud) { public ComputePackerResource (Account account, CloudService cloud) {
this.account = account; this.account = account;


+ 16
- 0
bubble-server/src/main/java/bubble/service/boot/ActivationService.java View File

@@ -18,6 +18,7 @@ import bubble.model.boot.ActivationRequest;
import bubble.model.boot.CloudServiceConfig; import bubble.model.boot.CloudServiceConfig;
import bubble.model.cloud.*; import bubble.model.cloud.*;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
import bubble.service.packer.PackerService;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.Cleanup; import lombok.Cleanup;
import lombok.Getter; import lombok.Getter;
@@ -64,6 +65,7 @@ public class ActivationService {
@Autowired private StandardSelfNodeService selfNodeService; @Autowired private StandardSelfNodeService selfNodeService;
@Autowired private BubbleConfiguration configuration; @Autowired private BubbleConfiguration configuration;
@Autowired private ModelSetupService modelSetupService; @Autowired private ModelSetupService modelSetupService;
@Autowired private PackerService packerService;


public BubbleNode bootstrapThisNode(Account account, ActivationRequest request) { public BubbleNode bootstrapThisNode(Account account, ActivationRequest request) {
String ip = getFirstPublicIpv4(); String ip = getFirstPublicIpv4();
@@ -228,12 +230,26 @@ public class ActivationService {
final Map<CrudOperation, Collection<Identifiable>> objects final Map<CrudOperation, Collection<Identifiable>> objects
= modelSetupService.setupModel(api, account, "manifest-defaults"); = modelSetupService.setupModel(api, account, "manifest-defaults");
log.info("bootstrapThisNode: created default objects\n"+json(objects)); log.info("bootstrapThisNode: created default objects\n"+json(objects));

if (!request.skipPacker()) initialPacker(account);

}, "ActivationService.bootstrapThisNode.createDefaultObjects"); }, "ActivationService.bootstrapThisNode.createDefaultObjects");

} else if (!request.skipPacker()) {
initialPacker(account);
} }


return node; return node;
} }


private void initialPacker(Account account) {
for (CloudService cloud : cloudDAO.findByAccountAndType(account.getUuid(), CloudServiceType.compute)) {
log.info("initialPacker: creating images for compute cloud: "+cloud.getName());
packerService.writePackerImages(cloud, AnsibleInstallType.sage, null);
packerService.writePackerImages(cloud, AnsibleInstallType.node, null);
}
}

public BubbleNetwork createRootNetwork(BubbleNetwork network) { public BubbleNetwork createRootNetwork(BubbleNetwork network) {
network.setUuid(ROOT_NETWORK_UUID); network.setUuid(ROOT_NETWORK_UUID);
return networkDAO.create(network); return networkDAO.create(network);


+ 5
- 5
bubble-server/src/main/java/bubble/service/boot/StandardSelfNodeService.java View File

@@ -15,10 +15,12 @@ import bubble.dao.cloud.BubbleNodeDAO;
import bubble.dao.cloud.BubbleNodeKeyDAO; import bubble.dao.cloud.BubbleNodeKeyDAO;
import bubble.dao.cloud.CloudServiceDAO; import bubble.dao.cloud.CloudServiceDAO;
import bubble.dao.device.DeviceDAO; import bubble.dao.device.DeviceDAO;
import bubble.model.account.Account;
import bubble.model.bill.AccountPlan; import bubble.model.bill.AccountPlan;
import bubble.model.bill.BubblePlan; import bubble.model.bill.BubblePlan;
import bubble.model.cloud.*;
import bubble.model.cloud.BubbleNetwork;
import bubble.model.cloud.BubbleNode;
import bubble.model.cloud.BubbleNodeKey;
import bubble.model.cloud.BubbleNodeState;
import bubble.model.cloud.notify.NotificationReceipt; import bubble.model.cloud.notify.NotificationReceipt;
import bubble.model.cloud.notify.NotificationType; import bubble.model.cloud.notify.NotificationType;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
@@ -29,7 +31,6 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.cache.Refreshable; import org.cobbzilla.util.cache.Refreshable;
import org.cobbzilla.util.daemon.SimpleDaemon;
import org.cobbzilla.util.http.HttpSchemes; import org.cobbzilla.util.http.HttpSchemes;
import org.cobbzilla.util.http.HttpUtil; import org.cobbzilla.util.http.HttpUtil;
import org.cobbzilla.util.io.FileUtil; import org.cobbzilla.util.io.FileUtil;
@@ -53,7 +54,6 @@ import static bubble.server.BubbleServer.disableRestoreMode;
import static bubble.server.BubbleServer.isRestoreMode; import static bubble.server.BubbleServer.isRestoreMode;
import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.function.Predicate.not;
import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.io.FileUtil.abs; import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.io.FileUtil.toFileOrDie; import static org.cobbzilla.util.io.FileUtil.toFileOrDie;
@@ -162,7 +162,7 @@ public class StandardSelfNodeService implements SelfNodeService {
background(() -> { background(() -> {
if (accountDAO.findAll() if (accountDAO.findAll()
.stream() .stream()
.filter(not(Account::isRoot))
.filter(a -> !accountDAO.isFirstAdmin(a))
.map(a -> deviceDAO.ensureAllSpareDevices(a.getUuid(), thisNetworkUuid)) .map(a -> deviceDAO.ensureAllSpareDevices(a.getUuid(), thisNetworkUuid))
.reduce(false, Boolean::logicalOr) .reduce(false, Boolean::logicalOr)
.booleanValue()) { .booleanValue()) {


+ 24
- 4
bubble-server/src/main/java/bubble/service/dbfilter/EntityIterator.java View File

@@ -7,9 +7,7 @@ package bubble.service.dbfilter;
import bubble.cloud.CloudServiceType; import bubble.cloud.CloudServiceType;
import bubble.cloud.storage.local.LocalStorageConfig; import bubble.cloud.storage.local.LocalStorageConfig;
import bubble.cloud.storage.local.LocalStorageDriver; import bubble.cloud.storage.local.LocalStorageDriver;
import bubble.model.account.Account;
import bubble.model.account.AccountSshKey;
import bubble.model.account.AccountTemplate;
import bubble.model.account.*;
import bubble.model.app.AppTemplateEntity; import bubble.model.app.AppTemplateEntity;
import bubble.model.app.BubbleApp; import bubble.model.app.BubbleApp;
import bubble.model.bill.AccountPaymentMethod; import bubble.model.bill.AccountPaymentMethod;
@@ -135,7 +133,29 @@ public abstract class EntityIterator implements Iterator<Identifiable> {
.forEach(this::add); .forEach(this::add);


} else if (Account.class.isAssignableFrom(c)) { } else if (Account.class.isAssignableFrom(c)) {
entities.forEach(e -> add(((Account) e).setPreferredPlan(null)));
entities.forEach(e -> {
if (network.hasAdminEmail() && network.getAccount().equals(e.getUuid())) {
final Account a = (Account) e;
a.setEmail(network.getAdminEmail());
}
add(((Account) e).setPreferredPlan(null));
});

} else if (AccountPolicy.class.isAssignableFrom(c) && network.hasAdminEmail()) {
entities.forEach(e -> {
if (network.hasAdminEmail()) {
final AccountPolicy p = (AccountPolicy) e;
if (p.getAccount().equals(network.getAccount())) {
final AccountContact adminContact = new AccountContact()
.setType(CloudServiceType.email)
.setInfo(network.getAdminEmail())
.setVerified(true)
.setRemovable(false);
p.setAccountContacts(new AccountContact[]{adminContact});
}
}
add(e);
});


} else if (AccountSshKey.class.isAssignableFrom(c)) { } else if (AccountSshKey.class.isAssignableFrom(c)) {
entities.forEach(e -> add(setInstallKey((AccountSshKey) e, network))); entities.forEach(e -> add(setInstallKey((AccountSshKey) e, network)));


+ 1
- 1
bubble-server/src/main/resources/messages

@@ -1 +1 @@
Subproject commit 6557ae47aae789ce6dc6ff1aca189787a89103bb
Subproject commit c0070a05666df680157c6740879ad5a3c98c3bba

+ 1
- 1
bubble-web

@@ -1 +1 @@
Subproject commit b7120e4099a382278c43813644f99e928c9ae828
Subproject commit 290017992084da8d799aba24409099d54c1a9305

+ 18
- 3
config/activation.json View File

@@ -54,9 +54,9 @@
} }
}, },


// You must configure the an SMTP service, it is required to send emails
// SendGrid, MailGun, or any other SMTP service should work fine
"SmtpServer": {
// You must configure an email service, it is required to send emails
// Comment out the email clouds that you don't use
"SmtpEmail": {
"config": { "config": {
"tlsEnabled": true "tlsEnabled": true
}, },
@@ -68,6 +68,21 @@
} }
}, },


"SendGridEmail": {
"config": {},
"credentials": {
"apiKey": "your_sendgrid_api_key"
}
},

"MailgunEmail": {
"config": {},
"credentials": {
"domain": "your_mailgun_domain",
"apiKey": "your_mailgun_api_key"
}
},

// Required for TOTP-based authentication. Nothing to configure, just leave this as-is // Required for TOTP-based authentication. Nothing to configure, just leave this as-is
"TOTPAuthenticator": {}, "TOTPAuthenticator": {},




+ 1
- 1
utils/cobbzilla-utils

@@ -1 +1 @@
Subproject commit d7ea904f35401302cbfb4bde5d70475d2721df07
Subproject commit d3b5e5254c2eca16290dc5746d0ab489160a8ff1

Loading…
Cancel
Save