@@ -9,6 +9,7 @@ import bubble.dao.account.AccountDAO; | |||||
import bubble.dao.account.AccountOwnedEntityDAO; | import bubble.dao.account.AccountOwnedEntityDAO; | ||||
import bubble.dao.cloud.BubbleNetworkDAO; | import bubble.dao.cloud.BubbleNetworkDAO; | ||||
import bubble.dao.cloud.CloudServiceDAO; | import bubble.dao.cloud.CloudServiceDAO; | ||||
import bubble.model.account.Account; | |||||
import bubble.model.bill.AccountPlan; | import bubble.model.bill.AccountPlan; | ||||
import bubble.model.bill.Bill; | import bubble.model.bill.Bill; | ||||
import bubble.model.bill.BubblePlan; | import bubble.model.bill.BubblePlan; | ||||
@@ -18,8 +19,10 @@ import bubble.model.cloud.CloudService; | |||||
import bubble.model.cloud.HostnameValidationResult; | import bubble.model.cloud.HostnameValidationResult; | ||||
import bubble.notify.payment.PaymentValidationResult; | import bubble.notify.payment.PaymentValidationResult; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.account.SyncAccountService; | |||||
import bubble.service.bill.RefundService; | import bubble.service.bill.RefundService; | ||||
import bubble.service.cloud.NetworkService; | import bubble.service.cloud.NetworkService; | ||||
import lombok.NonNull; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Repository; | import org.springframework.stereotype.Repository; | ||||
@@ -166,9 +169,20 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> { | |||||
paymentDriver.purchase(accountPlanUuid, paymentMethodUuid, billUuid); | paymentDriver.purchase(accountPlanUuid, paymentMethodUuid, billUuid); | ||||
}); | }); | ||||
} | } | ||||
final var account = accountDAO.findByUuid(accountPlan.getAccount()); | |||||
configuration.getBean(SyncAccountService.class).syncPlan(account, accountPlan); | |||||
return super.postCreate(accountPlan, context); | return super.postCreate(accountPlan, context); | ||||
} | } | ||||
@Override public AccountPlan postUpdate(@NonNull final AccountPlan accountPlan, @NonNull final Object context) { | |||||
final var account = accountDAO.findByUuid(accountPlan.getAccount()); | |||||
configuration.getBean(SyncAccountService.class).syncPlan(account, accountPlan); | |||||
return super.postUpdate(accountPlan, context); | |||||
} | |||||
@Override public void delete(String uuid) { | @Override public void delete(String uuid) { | ||||
final AccountPlan accountPlan = findByUuid(uuid); | final AccountPlan accountPlan = findByUuid(uuid); | ||||
if (accountPlan == null) return; | if (accountPlan == null) return; | ||||
@@ -193,6 +207,14 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> { | |||||
} | } | ||||
} | } | ||||
@Override public void forceDelete(String uuid) { super.delete(uuid); } | |||||
@Override public void forceDelete(String uuid) { | |||||
final AccountPlan accountPlan = findByUuid(uuid); | |||||
final Account account = accountDAO.findByUuid(accountPlan.getAccount()); | |||||
super.delete(uuid); | |||||
configuration.getBean(SyncAccountService.class).syncForceDeletedPlan(account, accountPlan.getName()); | |||||
} | |||||
public void forceDeleteWithoutSync(String uuid) { super.delete(uuid); } | |||||
} | } |
@@ -174,6 +174,9 @@ public class AccountPlan extends IdentifiableBase implements HasNetwork { | |||||
@Transient @Getter @Setter private transient Boolean sendMetrics = null; | @Transient @Getter @Setter private transient Boolean sendMetrics = null; | ||||
public boolean sendMetrics () { return sendMetrics == null || sendMetrics; } | public boolean sendMetrics () { return sendMetrics == null || sendMetrics; } | ||||
@JsonIgnore @Transient @Getter @Setter private Boolean skipSync; | |||||
public boolean skipSync() { return bool(skipSync); } | |||||
public BubbleNetwork bubbleNetwork(Account account, | public BubbleNetwork bubbleNetwork(Account account, | ||||
BubbleDomain domain, | BubbleDomain domain, | ||||
BubblePlan plan, | BubblePlan plan, | ||||
@@ -5,12 +5,14 @@ | |||||
package bubble.notify; | package bubble.notify; | ||||
import bubble.dao.account.AccountDAO; | import bubble.dao.account.AccountDAO; | ||||
import bubble.dao.bill.AccountPlanDAO; | |||||
import bubble.dao.cloud.BubbleNodeDAO; | import bubble.dao.cloud.BubbleNodeDAO; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.bill.AccountPlan; | |||||
import bubble.model.cloud.AnsibleInstallType; | import bubble.model.cloud.AnsibleInstallType; | ||||
import bubble.model.cloud.BubbleNode; | |||||
import bubble.model.cloud.notify.ReceivedNotification; | import bubble.model.cloud.notify.ReceivedNotification; | ||||
import bubble.service.account.SyncAccountNotification; | import bubble.service.account.SyncAccountNotification; | ||||
import lombok.NonNull; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
@@ -22,34 +24,74 @@ public class NotificationHandler_sync_account extends ReceivedNotificationHandle | |||||
@Autowired private BubbleNodeDAO nodeDAO; | @Autowired private BubbleNodeDAO nodeDAO; | ||||
@Autowired private AccountDAO accountDAO; | @Autowired private AccountDAO accountDAO; | ||||
@Autowired private AccountPlanDAO accountPlanDAO; | |||||
@Override public void handleNotification(ReceivedNotification n) { | |||||
final BubbleNode node = nodeDAO.findByUuid(n.getFromNode()); | |||||
@Override | |||||
public void handleNotification(ReceivedNotification n) { | |||||
final var node = nodeDAO.findByUuid(n.getFromNode()); | |||||
if (node == null) { | if (node == null) { | ||||
log.warn("sync_account: node not found: "+n.getFromNode()); | |||||
log.warn("sync_account: node not found: " + n.getFromNode()); | |||||
return; | |||||
} | |||||
final var notification = json(n.getPayloadJson(), SyncAccountNotification.class); | |||||
final var localAccount = accountDAO.findByUuid(notification.getAccountUuid()); | |||||
if (localAccount == null) { | |||||
reportError("sync_account: localAccount not found: " + notification.getAccountUuid()); | |||||
return; | |||||
} | |||||
if (!localAccount.sync()) { | |||||
log.info("sync_account: localAccount " + localAccount.getName() + " has sync disabled, no sync done"); | |||||
return; | |||||
} | |||||
if (notification.getHashedPassword().isPresent()) { | |||||
syncAccountPassword(localAccount, notification.getHashedPassword().get()); | |||||
} | |||||
if (notification.getAccountPlan().isPresent()) { | |||||
syncAccountPlan(localAccount.getUuid(), notification.getAccountPlan().get()); | |||||
} | |||||
if (notification.getForceDeletedAccountPlanName().isPresent()) { | |||||
syncForceDelAccountPlan(localAccount.getUuid(), notification.getForceDeletedAccountPlanName().get()); | |||||
} | |||||
} | |||||
private void syncAccountPassword(@NonNull final Account localAccount, | |||||
@NonNull final String incomingHashedPassword) { | |||||
localAccount.getHashedPassword().setHashedPassword(incomingHashedPassword); | |||||
// if we are a node, set skipSync so we don't get caught in an infinite loop | |||||
// (the node would notify the sage, which would notify the node, ad infinitum) | |||||
localAccount.setSkipSync(configuration.getThisNetwork().getInstallType() == AnsibleInstallType.node); | |||||
// update password, if we are a sage, this will notify all networks of password change | |||||
accountDAO.update(localAccount); | |||||
} | |||||
private void syncAccountPlan(@NonNull final String accountUuid, @NonNull final AccountPlan incomingPlan) { | |||||
final var localPlan = accountPlanDAO.findByAccountAndId(accountUuid, incomingPlan.getName()); | |||||
if (localPlan == null) { | |||||
// create new localPlan | |||||
final var newPlan = new AccountPlan(incomingPlan); | |||||
newPlan.setSkipSync(configuration.getThisNetwork().getInstallType() == AnsibleInstallType.node); | |||||
accountPlanDAO.create(newPlan); | |||||
} else { | |||||
// update localPlan | |||||
localPlan.update(incomingPlan); | |||||
localPlan.setSkipSync(configuration.getThisNetwork().getInstallType() == AnsibleInstallType.node); | |||||
accountPlanDAO.update(localPlan); | |||||
} | |||||
} | |||||
private void syncForceDelAccountPlan(@NonNull final String accountUuid, @NonNull final String planName) { | |||||
final var localPlan = accountPlanDAO.findByAccountAndId(accountUuid, planName); | |||||
if (configuration.getThisNetwork().getInstallType() == AnsibleInstallType.node) { | |||||
accountPlanDAO.forceDeleteWithoutSync(localPlan.getUuid()); | |||||
} else { | } else { | ||||
final SyncAccountNotification notification = json(n.getPayloadJson(), SyncAccountNotification.class); | |||||
final Account account = accountDAO.findByUuid(notification.getAccountUuid()); | |||||
if (account == null) { | |||||
reportError("sync_account: account not found: "+notification.getAccountUuid()); | |||||
return; | |||||
} | |||||
if (!account.sync()) { | |||||
log.info("sync_account: account "+account.getName()+" has sync disabled, not synchronizing"); | |||||
return; | |||||
} | |||||
account.getHashedPassword().setHashedPassword(notification.getHashedPassword()); | |||||
// if we are a node, set skipSync so we don't get caught in an infinite loop | |||||
// (the node would notify the sage, which would notify the node, ad infinitum) | |||||
if (configuration.getThisNetwork().getInstallType() == AnsibleInstallType.node) { | |||||
account.setSkipSync(true); | |||||
} | |||||
// update password, if we are a sage, this will notify all networks of password change | |||||
accountDAO.update(account); | |||||
accountPlanDAO.forceDelete(localPlan.getUuid()); | |||||
} | } | ||||
} | } | ||||
@@ -7,12 +7,14 @@ package bubble.service.account; | |||||
import bubble.dao.cloud.BubbleNetworkDAO; | import bubble.dao.cloud.BubbleNetworkDAO; | ||||
import bubble.dao.cloud.BubbleNodeDAO; | import bubble.dao.cloud.BubbleNodeDAO; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.bill.AccountPlan; | |||||
import bubble.model.cloud.AnsibleInstallType; | import bubble.model.cloud.AnsibleInstallType; | ||||
import bubble.model.cloud.BubbleNetwork; | import bubble.model.cloud.BubbleNetwork; | ||||
import bubble.model.cloud.BubbleNetworkState; | import bubble.model.cloud.BubbleNetworkState; | ||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.notify.NotificationService; | import bubble.service.notify.NotificationService; | ||||
import lombok.NonNull; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
@@ -28,51 +30,62 @@ public class StandardSyncAccountService implements SyncAccountService { | |||||
@Autowired private NotificationService notificationService; | @Autowired private NotificationService notificationService; | ||||
@Autowired private BubbleConfiguration configuration; | @Autowired private BubbleConfiguration configuration; | ||||
public void syncAccount(Account account) { | |||||
public void syncAccount(@NonNull final Account account) { | |||||
sync(account, new SyncAccountNotification(account)); | |||||
} | |||||
public void syncPlan(@NonNull final Account account, @NonNull final AccountPlan accountPlan) { | |||||
sync(account, new SyncAccountNotification(account.getUuid(), accountPlan)); | |||||
} | |||||
public void syncForceDeletedPlan(@NonNull final Account account, @NonNull final String accountPlanName) { | |||||
sync(account, new SyncAccountNotification(account.getUuid(), accountPlanName)); | |||||
} | |||||
private void sync(@NonNull final Account account, @NonNull final SyncAccountNotification notification) { | |||||
final BubbleNetwork thisNetwork = configuration.getThisNetwork(); | final BubbleNetwork thisNetwork = configuration.getThisNetwork(); | ||||
if (thisNetwork == null) { | if (thisNetwork == null) { | ||||
// should never happen | // should never happen | ||||
log.warn("syncAccount: thisNetwork was null, sync_account is impossible"); | |||||
log.warn("sync: thisNetwork was null, sync_account is impossible"); | |||||
return; | return; | ||||
} | } | ||||
if (!account.admin()) { | if (!account.admin()) { | ||||
log.info("syncAccount: not syncing non-admin password"); | |||||
log.info("sync: not syncing non-admin account"); | |||||
return; | return; | ||||
} | } | ||||
if (!account.sync()) { | if (!account.sync()) { | ||||
log.info("syncAccount: password sync disabled for account: "+account.getName()); | |||||
log.info("sync: account sync disabled for account: "+account.getName()); | |||||
return; | return; | ||||
} | } | ||||
final AnsibleInstallType installType = thisNetwork.getInstallType(); | final AnsibleInstallType installType = thisNetwork.getInstallType(); | ||||
final SyncAccountNotification notification = new SyncAccountNotification(account); | |||||
if (installType == AnsibleInstallType.sage) { | if (installType == AnsibleInstallType.sage) { | ||||
// changing password on sage, notify all bubbles launched by user that have syncAccount == true | |||||
// changing account on sage, notify all bubbles launched by user that have syncAccount == true | |||||
for (BubbleNetwork network : networkDAO.findByAccount(account.getUuid())) { | for (BubbleNetwork network : networkDAO.findByAccount(account.getUuid())) { | ||||
if (network.getState() != BubbleNetworkState.running) continue; | if (network.getState() != BubbleNetworkState.running) continue; | ||||
if (!network.syncAccount()) continue; | if (!network.syncAccount()) continue; | ||||
for (BubbleNode node : nodeDAO.findByNetwork(network.getUuid())) { | for (BubbleNode node : nodeDAO.findByNetwork(network.getUuid())) { | ||||
if (node.getUuid().equals(configuration.getThisNode().getUuid())) { | if (node.getUuid().equals(configuration.getThisNode().getUuid())) { | ||||
log.info("syncAccount: not notifying self"); | |||||
log.info("sync: not notifying self"); | |||||
continue; | continue; | ||||
} | } | ||||
log.info("syncAccount: sending sync_account notification from sage to node: "+node.id()); | |||||
log.info("sync: sending sync_account notification from sage to node: "+node.id()); | |||||
notificationService.notify(node, sync_account, notification); | notificationService.notify(node, sync_account, notification); | ||||
} | } | ||||
} | } | ||||
} else if (installType == AnsibleInstallType.node) { | } else if (installType == AnsibleInstallType.node) { | ||||
if (!thisNetwork.syncAccount()) { | if (!thisNetwork.syncAccount()) { | ||||
log.info("syncAccount: disabled for node, not sending sync_account notification"); | |||||
log.info("sync: disabled for node, not sending sync_account notification"); | |||||
return; | return; | ||||
} | } | ||||
// changing password on node, notify sage, which will then notify all bubbles launched by user that have | |||||
// changing account on node, notify sage, which will then notify all bubbles launched by user that have | |||||
// syncAccount == true | // syncAccount == true | ||||
log.info("syncAccount: sending sync_account notification from node to sage: "+configuration.getSageNode()); | |||||
log.info("sync: sending sync_account notification from node to sage: "+configuration.getSageNode()); | |||||
notificationService.notify(configuration.getSageNode(), sync_account, notification); | notificationService.notify(configuration.getSageNode(), sync_account, notification); | ||||
} else { | } else { | ||||
reportError("syncAccount("+account.getEmail()+"/"+account.getUuid()+"): invalid installType: "+installType); | |||||
reportError("sync("+account.getEmail()+"/"+account.getUuid()+"): invalid installType: "+installType); | |||||
} | } | ||||
} | } | ||||
@@ -5,20 +5,35 @@ | |||||
package bubble.service.account; | package bubble.service.account; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.bill.AccountPlan; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.NonNull; | |||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import java.util.Optional; | |||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class SyncAccountNotification { | public class SyncAccountNotification { | ||||
@Getter @Setter private String accountUuid; | @Getter @Setter private String accountUuid; | ||||
@Getter @Setter private String hashedPassword; | |||||
@Getter @Setter private Optional<String> hashedPassword = Optional.empty(); | |||||
@Getter @Setter private Optional<AccountPlan> accountPlan = Optional.empty(); | |||||
@Getter @Setter private Optional<String> forceDeletedAccountPlanName = Optional.empty(); | |||||
public SyncAccountNotification(Account account) { | |||||
public SyncAccountNotification(@NonNull final Account account) { | |||||
this.accountUuid = account.getUuid(); | this.accountUuid = account.getUuid(); | ||||
this.hashedPassword = account.getHashedPassword().getHashedPassword(); | |||||
this.hashedPassword = Optional.of(account.getHashedPassword().getHashedPassword()); | |||||
} | |||||
public SyncAccountNotification(@NonNull final String accountUuid, @NonNull final AccountPlan accountPlan) { | |||||
this.accountUuid = accountUuid; | |||||
this.accountPlan = Optional.of(accountPlan); | |||||
} | } | ||||
public SyncAccountNotification(@NonNull final String accountUuid, @NonNull final String forceDeletedPlanUuid) { | |||||
this.accountUuid = accountUuid; | |||||
this.forceDeletedAccountPlanName = Optional.of(forceDeletedPlanUuid); | |||||
} | |||||
} | } |
@@ -5,9 +5,13 @@ | |||||
package bubble.service.account; | package bubble.service.account; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.bill.AccountPlan; | |||||
import lombok.NonNull; | |||||
public interface SyncAccountService { | public interface SyncAccountService { | ||||
void syncAccount(Account account); | |||||
void syncAccount(@NonNull final Account account); | |||||
void syncPlan(@NonNull final Account account, @NonNull final AccountPlan accountPlan); | |||||
void syncForceDeletedPlan(@NonNull final Account account, @NonNull final String accountPlanName); | |||||
} | } |
@@ -5,7 +5,9 @@ | |||||
package bubble.service_dbfilter; | package bubble.service_dbfilter; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.bill.AccountPlan; | |||||
import bubble.service.account.SyncAccountService; | import bubble.service.account.SyncAccountService; | ||||
import lombok.NonNull; | |||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | ||||
@@ -13,6 +15,13 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | |||||
@Service | @Service | ||||
public class DbFilterSyncAccountService implements SyncAccountService { | public class DbFilterSyncAccountService implements SyncAccountService { | ||||
@Override public void syncAccount(Account account) { notSupported("syncAccount"); } | |||||
@Override public void syncAccount(@NonNull final Account account) { notSupported("syncAccount"); } | |||||
@Override public void syncPlan(@NonNull final Account account, @NonNull final AccountPlan accountPlan) { | |||||
notSupported("syncPlan"); | |||||
} | |||||
@Override public void syncForceDeletedPlan(@NonNull final Account account, @NonNull final String accountPlanName) { | |||||
notSupported("syncForceDeletedPlan"); | |||||
} | |||||
} | } |