diff --git a/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java b/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java index 4a612898..2d574c5d 100644 --- a/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java +++ b/bubble-server/src/main/java/bubble/dao/account/AccountDAO.java @@ -152,11 +152,8 @@ public class AccountDAO extends AbstractCRUDDAO implements SqlViewSearc // 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 if (!account.isRoot() && configuration.hasSageNode() && !configuration.isSelfSage()) { - background(() -> { - if (deviceDAO.ensureAllSpareDevices(account.getUuid(), configuration.getThisNetwork().getUuid())) { - deviceDAO.refreshVpnUsers(); - } - }); + deviceDAO.ensureAllSpareDevices(account.getUuid(), configuration.getThisNetwork().getUuid()); + deviceDAO.refreshVpnUsers(); } if (account.hasParent()) { diff --git a/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java b/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java index 93d20321..3db2a0bf 100644 --- a/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java +++ b/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java @@ -23,9 +23,10 @@ import java.util.List; import static bubble.ApiConstants.HOME_DIR; import static bubble.model.device.Device.UNINITIALIZED_DEVICE_LIKE; import static bubble.model.device.Device.newUninitializedDevice; -import static org.cobbzilla.util.daemon.ZillaRuntime.background; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.io.FileUtil.abs; import static org.cobbzilla.util.io.FileUtil.touch; +import static org.cobbzilla.util.reflect.ReflectionUtil.copy; @Repository @Slf4j public class DeviceDAO extends AccountOwnedEntityDAO { @@ -58,43 +59,40 @@ public class DeviceDAO extends AccountOwnedEntityDAO { @Transactional @Override public Device create(@NonNull final Device device) { if (device.uninitialized()) return super.create(device); + device.initDeviceType(); final var accountUuid = device.getAccount(); final var uninitializedDevices = findByAccountAndUninitialized(accountUuid); + if (uninitializedDevices.size() < SPARE_DEVICES_PER_ACCOUNT_THRESHOLD + && !configuration.getBean(AccountDAO.class).findByUuid(accountUuid).isRoot()) { + ensureAllSpareDevices(accountUuid, device.getNetwork()); + } + + final Device result; + // run the above creation of spare devices in parallel, but if there were no spare devices loaded before that, + // create a brand new entry here: if (uninitializedDevices.isEmpty()) { log.info("create: no uninitialized devices for account " + accountUuid); - background(() -> { - if (!configuration.getBean(AccountDAO.class).findByUuid(accountUuid).isRoot()) { - ensureAllSpareDevices(accountUuid, device.getNetwork()); - } - }); // just create the device now: device.initTotpKey(); - device.initDeviceType(); - final var d = super.create(device); - refreshVpnUsers(); - return d; + result = super.create(device); } else { - final var d = uninitializedDevices.get(0); - d.initialize(device); - return update(d); - // update will do refreshVpnUsers + final var uninitialized = uninitializedDevices.get(0); + copy(uninitialized, device); + result = super.update(uninitialized); } + + refreshVpnUsers(); + return result; } @Override @NonNull public Device update(@NonNull final Device updateRequest) { - final var old = findByUuid(updateRequest.getUuid()); - - if (old != null && old.uninitialized()) updateRequest.initTotpKey(); - updateRequest.initDeviceType(); - - final var updated = super.update(updateRequest); - if (old.uninitialized() && !updated.uninitialized() - && findByAccountAndUninitialized(old.getAccount()).size() < SPARE_DEVICES_PER_ACCOUNT_THRESHOLD) { - background(() -> ensureAllSpareDevices(old.getAccount(), old.getNetwork())); - } + final var toUpdate = (Device) readOnlySession().get(Device.class, updateRequest.getUuid()); + if (toUpdate == null) die("Cannot find device to update with uuid: " + updateRequest.getUuid()); + if (toUpdate.uninitialized()) die("Cannot update special devices: " + updateRequest.getName()); + final var updated = super.update(toUpdate); refreshVpnUsers(); return updated; } @@ -102,12 +100,10 @@ public class DeviceDAO extends AccountOwnedEntityDAO { @Override public void delete(String uuid) { final Device device = findByUuid(uuid); if (device != null) { + if (device.uninitialized()) die("Cannot delete special device: " + device.getName()); + dataDAO.deleteDevice(uuid); super.delete(uuid); - if (device.uninitialized() && findByAccountAndUninitialized(device.getAccount()).size() - < SPARE_DEVICES_PER_ACCOUNT_THRESHOLD) { - background(() -> ensureAllSpareDevices(device.getAccount(), device.getNetwork())); - } refreshVpnUsers(); } } diff --git a/bubble-server/src/main/java/bubble/model/device/Device.java b/bubble-server/src/main/java/bubble/model/device/Device.java index 99cbd402..8221390d 100644 --- a/bubble-server/src/main/java/bubble/model/device/Device.java +++ b/bubble-server/src/main/java/bubble/model/device/Device.java @@ -66,11 +66,6 @@ public class Device extends IdentifiableBase implements HasAccount { @Override public Identifiable update(Identifiable thing) { copy(this, thing, UPDATE_FIELDS); return this; } - public void initialize (Device other) { - copy(this, other); - initTotpKey(); - } - public Device initDeviceType() { if (empty(getDeviceType()) || getDeviceType().equals(uninitialized)) setDeviceType(other); return this; @@ -106,7 +101,8 @@ public class Device extends IdentifiableBase implements HasAccount { @Size(max=300, message="err.totpKey.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(300+ENC_PAD)+") NOT NULL") @JsonIgnore @Getter @Setter private String totpKey; - public Device initTotpKey() { return setTotpKey(randomAlphanumeric(200)); } + public Device initTotpKey() { return hasTotpKey() ? this : setTotpKey(randomAlphanumeric(200)); } + public boolean hasTotpKey() { return !empty(totpKey); } // make ctime visible @JsonProperty public long getCtime () { return super.getCtime(); } diff --git a/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java b/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java index 04691cf9..092706ec 100644 --- a/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java +++ b/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java @@ -106,7 +106,7 @@ public class DeviceIdService { } else { log.warn("findDeviceByIp("+ipAddr+") test mode, returning and possibly initializing first admin device"); final Device device = adminDevices.get(0); - return device.uninitialized() ? deviceDAO.update(device.initTotpKey()) : device; + return !device.hasTotpKey() ? deviceDAO.update(device.initTotpKey()) : device; } }