瀏覽代碼

Merge branch 'master' into kris/add_support_for_restore_ui

# Conflicts:
#	bubble-web
pull/20/head
Kristijan Mitrovic 4 年之前
父節點
當前提交
01c4f30a7d
共有 58 個檔案被更改,包括 302 行新增286 行删除
  1. +2
    -2
      bubble-server/src/main/java/bubble/app/analytics/TrafficAnalyticsAppDataDriver.java
  2. +2
    -1
      bubble-server/src/main/java/bubble/cloud/CloudRegion.java
  3. +0
    -1
      bubble-server/src/main/java/bubble/cloud/email/EmailServiceDriver.java
  4. +1
    -1
      bubble-server/src/main/java/bubble/cloud/payment/promo/firstMonthFree/FirstMonthFreePaymentDriver.java
  5. +5
    -5
      bubble-server/src/main/java/bubble/cloud/payment/promo/referralMonthFree/ReferralMonthFreePaymentDriver.java
  6. +16
    -5
      bubble-server/src/main/java/bubble/dao/account/AccountDAO.java
  7. +22
    -36
      bubble-server/src/main/java/bubble/model/account/Account.java
  8. +27
    -6
      bubble-server/src/main/java/bubble/model/account/AccountContact.java
  9. +18
    -0
      bubble-server/src/main/java/bubble/model/account/AccountLoginRequest.java
  10. +4
    -3
      bubble-server/src/main/java/bubble/model/account/AccountPolicy.java
  11. +6
    -3
      bubble-server/src/main/java/bubble/model/account/AccountRegistration.java
  12. +1
    -1
      bubble-server/src/main/java/bubble/model/account/TotpBean.java
  13. +14
    -2
      bubble-server/src/main/java/bubble/model/account/message/handlers/AccountVerifyHandler.java
  14. +6
    -3
      bubble-server/src/main/java/bubble/model/boot/ActivationRequest.java
  15. +9
    -2
      bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java
  16. +1
    -1
      bubble-server/src/main/java/bubble/resources/IdentityResource.java
  17. +13
    -12
      bubble-server/src/main/java/bubble/resources/account/AccountsResource.java
  18. +14
    -21
      bubble-server/src/main/java/bubble/resources/account/AuthResource.java
  19. +2
    -2
      bubble-server/src/main/java/bubble/resources/message/MessagesResource.java
  20. +3
    -3
      bubble-server/src/main/java/bubble/rule/TrafficRecord.java
  21. +1
    -0
      bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalyticsRuleDriver.java
  22. +1
    -1
      bubble-server/src/main/java/bubble/server/listener/BubbleFirstTimeListener.java
  23. +1
    -1
      bubble-server/src/main/java/bubble/server/listener/DeviceInitializerListener.java
  24. +1
    -1
      bubble-server/src/main/java/bubble/service/account/StandardSyncPasswordService.java
  25. +1
    -1
      bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties
  26. +2
    -1
      bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties
  27. +1
    -1
      bubble-server/src/test/java/bubble/test/ActivatedBubbleModelTestBase.java
  28. +4
    -6
      bubble-server/src/test/resources/models/include/new_account.json
  29. +1
    -2
      bubble-server/src/test/resources/models/include/referral_signup.json
  30. +1
    -1
      bubble-server/src/test/resources/models/system/account_testDevice.json
  31. +0
    -2
      bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account.json
  32. +2
    -3
      bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account_with_payments.json
  33. +1
    -3
      bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account.json
  34. +2
    -3
      bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account_with_payments.json
  35. +0
    -1
      bubble-server/src/test/resources/models/tests/auth/account_crud.json
  36. +14
    -17
      bubble-server/src/test/resources/models/tests/auth/account_registration.json
  37. +2
    -2
      bubble-server/src/test/resources/models/tests/auth/basic_auth.json
  38. +6
    -7
      bubble-server/src/test/resources/models/tests/auth/change_admin_password.json
  39. +12
    -14
      bubble-server/src/test/resources/models/tests/auth/change_password.json
  40. +1
    -5
      bubble-server/src/test/resources/models/tests/auth/device_crud.json
  41. +3
    -5
      bubble-server/src/test/resources/models/tests/auth/download_account.json
  42. +6
    -8
      bubble-server/src/test/resources/models/tests/auth/forgot_password.json
  43. +10
    -12
      bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json
  44. +4
    -5
      bubble-server/src/test/resources/models/tests/live/backup_and_restore.json
  45. +6
    -8
      bubble-server/src/test/resources/models/tests/network/network_regions.json
  46. +4
    -6
      bubble-server/src/test/resources/models/tests/network/simple_network.json
  47. +3
    -4
      bubble-server/src/test/resources/models/tests/payment/pay_code.json
  48. +4
    -5
      bubble-server/src/test/resources/models/tests/payment/pay_credit.json
  49. +5
    -6
      bubble-server/src/test/resources/models/tests/payment/pay_credit_refund_and_restart.json
  50. +3
    -4
      bubble-server/src/test/resources/models/tests/payment/pay_free.json
  51. +3
    -4
      bubble-server/src/test/resources/models/tests/payment/plan_apps.json
  52. +6
    -7
      bubble-server/src/test/resources/models/tests/payment/recurring_billing.json
  53. +12
    -13
      bubble-server/src/test/resources/models/tests/promo/account_credit.json
  54. +2
    -3
      bubble-server/src/test/resources/models/tests/promo/first_month_free.json
  55. +4
    -5
      bubble-server/src/test/resources/models/tests/promo/multi_promo.json
  56. +5
    -7
      bubble-server/src/test/resources/models/tests/promo/referral_month_free.json
  57. +1
    -1
      bubble-web
  58. +1
    -1
      utils/cobbzilla-wizard

+ 2
- 2
bubble-server/src/main/java/bubble/app/analytics/TrafficAnalyticsAppDataDriver.java 查看文件

@@ -11,8 +11,8 @@ import bubble.model.app.BubbleApp;
import bubble.model.app.config.AppDataDriverBase;
import bubble.model.app.config.AppDataView;
import bubble.model.device.Device;
import bubble.rule.TrafficRecord;
import bubble.rule.analytics.TrafficAnalyticsRuleDriver;
import bubble.rule.analytics.TrafficRecord;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.network.NetworkUtil;
@@ -125,7 +125,7 @@ public class TrafficAnalyticsAppDataDriver extends AppDataDriverBase {
+ fileExt(TYPICAL_WEB_TYPES[nextInt(0, TYPICAL_WEB_TYPES.length)]))
.setReferer("NONE")
.setAccountUuid(caller.getUuid())
.setAccountName(caller.getName())
.setAccountEmail(caller.getEmail())
.setDeviceUuid(device.getUuid())
.setDeviceName(device.getName()),
getRecentTraffic());


+ 2
- 1
bubble-server/src/main/java/bubble/cloud/CloudRegion.java 查看文件

@@ -7,11 +7,12 @@ package bubble.cloud;
import bubble.cloud.geoLocation.GeoLocation;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import static java.util.UUID.randomUUID;

@Accessors(chain=true)
@Accessors(chain=true) @ToString(of={"cloud","name","internalName"})
public class CloudRegion {

public static final CloudRegion[] EMPTY_REGIONS = new CloudRegion[0];


+ 0
- 1
bubble-server/src/main/java/bubble/cloud/email/EmailServiceDriver.java 查看文件

@@ -32,7 +32,6 @@ public interface EmailServiceDriver extends AuthenticationDriver {

final RenderedEmail email = new RenderedEmail(ctx);
email.setToEmail(contact.getInfo());
email.setToName(account.getName());
email.setFromEmail(AuthenticationDriver.render("fromEmail", ctx, message, configuration, templatePath));
email.setFromName(AuthenticationDriver.render("fromName", ctx, message, configuration, templatePath));
email.setSubject(AuthenticationDriver.render("subject", ctx, message, configuration, templatePath));


+ 1
- 1
bubble-server/src/main/java/bubble/cloud/payment/promo/firstMonthFree/FirstMonthFreePaymentDriver.java 查看文件

@@ -29,7 +29,7 @@ public class FirstMonthFreePaymentDriver extends PromotionalPaymentDriverBase<Pr
// does the caller already have one of these?
final List<AccountPaymentMethod> existingCreditPaymentMethods = paymentMethodDAO.findByAccountAndCloud(caller.getUuid(), promo.getCloud());
if (!empty(existingCreditPaymentMethods)) {
log.warn("applyPromo: promo="+promo.getName()+", account="+caller.getName()+", account already has one of these promos applied");
log.warn("applyPromo: promo="+promo.getName()+", account="+caller.getEmail()+", account already has one of these promos applied");
return true; // promo has already been applied, return true
}
paymentMethodDAO.create(new AccountPaymentMethod()


+ 5
- 5
bubble-server/src/main/java/bubble/cloud/payment/promo/referralMonthFree/ReferralMonthFreePaymentDriver.java 查看文件

@@ -41,7 +41,7 @@ public class ReferralMonthFreePaymentDriver extends PromotionalPaymentDriverBase
// caller must not have any bills
final int billCount = billDAO.countByAccount(caller.getUuid());
if (billCount != 0) {
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", account must have no Bills, found "+billCount+" bills");
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", account must have no Bills, found "+billCount+" bills");
return false;
}

@@ -51,7 +51,7 @@ public class ReferralMonthFreePaymentDriver extends PromotionalPaymentDriverBase
// It's OK for the referredFrom user to have many of these, as long as there is not one for this user
for (AccountPaymentMethod apm : referredFromCreditPaymentMethods) {
if (apm.getPaymentInfo().equals(caller.getUuid())) {
log.error("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", referredFrom="+referredFrom.getName()+" has already referred this caller");
log.error("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", referredFrom="+referredFrom.getEmail()+" has already referred this caller");
return false;
}
}
@@ -59,18 +59,18 @@ public class ReferralMonthFreePaymentDriver extends PromotionalPaymentDriverBase
// does the caller already have one of these?
final List<AccountPaymentMethod> existingCreditPaymentMethods = paymentMethodDAO.findByAccountAndCloud(caller.getUuid(), promo.getCloud());
if (!empty(existingCreditPaymentMethods)) {
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", account already has one of these promos applied");
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", account already has one of these promos applied");
return true; // promo has already been applied, return true
}

// sanity check before using
final ReferralCode ref = referralCodeDAO.findByUuid(referralCode.getUuid());
if (ref == null) {
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", referralCode "+referralCode.getUuid()+" was not found");
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", referralCode "+referralCode.getUuid()+" was not found");
return false;
}
if (ref.claimed()) {
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getName()+", referralCode "+referralCode.getUuid()+" has already been claimed by "+ref.getClaimedByUuid());
log.warn("applyReferralPromo: promo="+promo.getName()+", account="+caller.getEmail()+", referralCode "+referralCode.getUuid()+" has already been claimed by "+ref.getClaimedByUuid());
return false;
}



+ 16
- 5
bubble-server/src/main/java/bubble/dao/account/AccountDAO.java 查看文件

@@ -5,6 +5,7 @@
package bubble.dao.account;

import bubble.cloud.CloudServiceDriver;
import bubble.cloud.CloudServiceType;
import bubble.cloud.compute.ComputeNodeSizeType;
import bubble.dao.account.message.AccountMessageDAO;
import bubble.dao.app.*;
@@ -46,6 +47,7 @@ import static bubble.model.account.AutoUpdatePolicy.EMPTY_AUTO_UPDATE_POLICY;
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE;
import static java.lang.Thread.currentThread;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.cobbzilla.util.daemon.ZillaRuntime.bool;
import static org.cobbzilla.util.daemon.ZillaRuntime.daemon;
import static org.cobbzilla.wizard.model.IdentifiableBase.CTIME_ASC;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
@@ -75,18 +77,25 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc
@Autowired private ReferralCodeDAO referralCodeDAO;

public Account newAccount(Request req, Account caller, AccountRegistration request, Account parent) {
final AccountContact contact = new AccountContact()
.setType(CloudServiceType.email)
.setInfo(request.getEmail())
.setRemovable(false)
.setReceiveInformationalMessages(bool(request.getReceiveInformationalMessages()))
.setReceivePromotionalMessages(bool(request.getReceivePromotionalMessages()));

return create(new Account(request)
.setAdmin(caller != null && caller.admin() && request.admin()) // only admins can create other admins
.setRemoteHost(getRemoteHost(req))
.setParent(parent.getUuid())
.setPolicy(new AccountPolicy().setContact(request.getContact())));
.setPolicy(new AccountPolicy().setContact(contact, null, configuration)));
}

public Account findByName(String name) { return findByUniqueField("name", name); }
public Account findByEmail(String email) { return findByUniqueField("email", email.trim()); }

public Account findById(String id) {
final Account found = findByUuid(id);
return found != null ? found : findByUniqueField("name", id);
return found != null ? found : findByUniqueField("email", id);
}

@Override public Object preCreate(Account account) {
@@ -97,11 +106,13 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc
if (plan != null && plan.hasMaxAccounts()) {
final int numAccounts = countNotDeleted();
if (numAccounts >= plan.getMaxAccounts()) {
throw invalidEx("err.name.planMaxAccountLimit", "Account limit for plan reached (max "+plan.getMaxAccounts()+" accounts)");
throw invalidEx("err.plan.planMaxAccountLimit", "Account limit for plan reached (max "+plan.getMaxAccounts()+" accounts)");
}
}

final ValidationResult result = account.validateName();
// if activated is false, then we are creating the root account
// do not validate, it's not a valid email address and that is ok.
final ValidationResult result = !activated() ? new ValidationResult() : account.validateEmail();
if (result.isInvalid()) throw invalidEx(result);

if (account.getAutoUpdatePolicy() == null) {


+ 22
- 36
bubble-server/src/main/java/bubble/model/account/Account.java 查看文件

@@ -40,9 +40,7 @@ import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Transient;
import javax.validation.constraints.Size;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

import static bubble.ApiConstants.ACCOUNTS_ENDPOINT;
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE;
@@ -50,6 +48,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
import static org.cobbzilla.util.string.ValidationRegexes.EMAIL_PATTERN;
import static org.cobbzilla.util.system.Sleep.sleep;
import static org.cobbzilla.util.time.TimeUtil.formatDuration;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING;
@@ -60,8 +59,8 @@ import static org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKeySe
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;

@ECType(root=true)
@ECTypeURIs(baseURI=ACCOUNTS_ENDPOINT, listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false)
@ECTypeChildren(uriPrefix=ACCOUNTS_ENDPOINT+"/{Account.name}", value={
@ECTypeURIs(baseURI=ACCOUNTS_ENDPOINT, listFields={"email", "url", "description", "admin", "suspended"}, isDeleteDefined=false)
@ECTypeChildren(uriPrefix=ACCOUNTS_ENDPOINT+"/{Account.email}", value={
@ECTypeChild(type=Device.class, backref="account"),
@ECTypeChild(type=RuleDriver.class, backref="account"),
@ECTypeChild(type=BubbleApp.class, backref="account"),
@@ -89,8 +88,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
"name", "referralCode", "termsAgreed", "preferredPlan");

public static final String ROOT_USERNAME = "root";
public static final int NAME_MIN_LENGTH = 4;
public static final int NAME_MAX_LENGTH = 100;
public static final int EMAIL_MAX_LENGTH = 100;

public static Account sageMask(Account sage) {
final Account masked = new Account(sage)
@@ -106,22 +104,14 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
public static String accountField(String table) { return table.equalsIgnoreCase("account") ? UUID : "account"; }

@ECSearchable(filter=true) @ECField(index=10)
@HasValue(message="err.name.required")
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=NAME_MAX_LENGTH)
@Getter private String name;
public Account setName (String n) { this.name = n == null ? null : n.toLowerCase(); return this; }
public boolean hasName () { return !empty(name); }
@HasValue(message="err.email.required")
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=EMAIL_MAX_LENGTH)
@Getter private String email;
public Account setEmail(String n) { this.email = n == null ? null : n.toLowerCase().trim(); return this; }
public boolean hasEmail() { return !empty(email); }

public static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[A-Za-z][-\\.A-Za-z0-9_]+$");
public boolean hasInvalidName() { return hasName() && !VALID_NAME_PATTERN.matcher(getName()).matches(); }

private static final List<String> RESERVED_NAMES = Arrays.asList(
"root", "postmaster", "hostmaster", "webmaster",
"dns", "dnscrypt", "dnscrypt-proxy", "ftp", "www", "www-data", "postgres", "ipfs",
"redis", "nginx", "mitmproxy", "mitmdump", "algo", "algovpn");
public boolean hasReservedName () { return hasName() && isReservedName(getName()); }

public static boolean isReservedName(String name) { return RESERVED_NAMES.contains(name); }
@Override @Transient public String getName() { return getEmail(); }
public Account setName(String n) { return setEmail(n); }

// 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?)
@@ -244,7 +234,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
public Account(Account other) { copy(this, other, CREATE_FIELDS); }

public Account(ActivationRequest request) {
setName(request.getName());
setEmail(request.getEmail());
setHashedPassword(new HashedPassword(request.getPassword()));
setAdmin(true);
setDescription(request.hasDescription() ? request.getDescription() : "root user");
@@ -253,7 +243,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
}

public Account(AccountRegistration request) {
setName(request.getName());
setEmail(request.getEmail());
setHashedPassword(new HashedPassword(request.getPassword()));
setTermsAgreed(request.getTermsAgreed());
setPreferredPlan(request.getPreferredPlan());
@@ -289,21 +279,17 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci
@Transient @Getter @Setter private transient String remoteHost;
@Transient @JsonIgnore @Getter @Setter private transient Boolean verifyContact;

public ValidationResult validateName () {
public ValidationResult validateEmail() { return validateEmail(getEmail()); }

public static ValidationResult validateEmail(String email) {
final ValidationResult result = new ValidationResult();
if (!hasName()) {
result.addViolation("err.name.required");
if (empty(email)) {
result.addViolation("err.email.required");
} else {
if (getName().length() < NAME_MIN_LENGTH) {
result.addViolation("err.name.tooShort");
} else if (getName().length() > NAME_MAX_LENGTH) {
result.addViolation("err.name.tooLong");
}
if (!admin() && hasReservedName()) {
result.addViolation("err.name.reserved");
}
if (hasInvalidName()) {
result.addViolation("err.name.regexFailed");
if (!EMAIL_PATTERN.matcher(email).matches()) {
result.addViolation("err.email.invalid");
} else if (email.length() > EMAIL_MAX_LENGTH) {
result.addViolation("err.email.tooLong");
}
}
return result;


+ 27
- 6
bubble-server/src/main/java/bubble/model/account/AccountContact.java 查看文件

@@ -5,6 +5,7 @@
package bubble.model.account;

import bubble.cloud.CloudServiceType;
import bubble.dao.account.AccountDAO;
import bubble.model.account.message.AccountAction;
import bubble.model.account.message.AccountMessage;
import bubble.model.account.message.AccountMessageType;
@@ -40,7 +41,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
public class AccountContact implements Serializable {

public static final int MAX_NICK_LENGTH = 100;
public static final String[] UPDATE_EXCLUDE_FIELDS = {UUID, "type", "info"};
public static final String[] UPDATE_EXCLUDE_FIELDS = {UUID, "type", "info", "removable"};

public AccountContact(AccountContact other) { copy(this, other); }

@@ -67,12 +68,15 @@ public class AccountContact implements Serializable {
@Getter @Setter private CloudServiceType type;
@JsonIgnore public boolean isAuthenticator () { return type == CloudServiceType.authenticator; }
@JsonIgnore public boolean isNotAuthenticator () { return !isAuthenticator(); }
@JsonIgnore public boolean getIsEmail () { return type == CloudServiceType.email; }
@JsonIgnore public boolean getIsSms () { return type == CloudServiceType.sms; }
@JsonIgnore public boolean isEmail () { return type == CloudServiceType.email; }
@JsonIgnore public boolean isSms () { return type == CloudServiceType.sms; }

@Getter @Setter private Boolean verified = null;
public boolean verified () { return bool(verified); }

@Getter @Setter private Boolean removable = true;
public boolean removable () { return bool(removable); }

@Getter @Setter private Boolean requiredForNetworkOperations = true;
@Getter @Setter private Boolean requiredForAccountOperations = true;

@@ -107,7 +111,12 @@ public class AccountContact implements Serializable {

if (!c.hasUuid()) c.initUuid();

if (contacts == null) contacts = new AccountContact[0];
if (contacts == null) {
contacts = new AccountContact[0];
c.setRemovable(false); // first contact is not removable
} else {
c.setRemovable(true); // all other contacts are removable
}
final AccountContact existing = Arrays.stream(contacts).filter(contactMatch(c)).findFirst().orElse(null);
if (existing != null) {
// updating a contact -- cannot set authFactor if verification is still required
@@ -125,6 +134,14 @@ public class AccountContact implements Serializable {
existing.update(c);

} else {
if (c.isEmail()) {
// has another user registered this as their primary email?
final Account registeredWithEmail = configuration.getBean(AccountDAO.class).findByEmail(c.getInfo());
if (registeredWithEmail != null && (account == null || !registeredWithEmail.getUuid().equals(account.getUuid()))) {
throw invalidEx("err.email.registered", "Email already registered");
}
}

// creating a new contact -- cannot set authFactor for contacts requiring verification
if (c.getType().isVerifiableAuthenticationType() && c.authFactor()) {
throw invalidEx("err.contact.unverified", "cannot set authFactor on an unverified contact; verify first");
@@ -164,10 +181,14 @@ public class AccountContact implements Serializable {
.orElse(null);
}

public static AccountContact[] remove(AccountContact contact, AccountContact[] contacts) {
public static AccountContact[] remove(Account account, AccountContact contact, AccountContact[] contacts) {
if (contacts == null || contacts.length == 0) return contacts;
if (contact.getType() == CloudServiceType.email && contact.getInfo().equals(account.getEmail())) {
log.warn("remove: cannot remove account.email contact");
return contacts;
}
final List<AccountContact> contactList = new ArrayList<>(Arrays.asList(contacts));
return contactList.removeIf(contactMatch(contact))
return contactList.removeIf(c -> c.removable() && contactMatch(contact).test(c))
? contactList.toArray(new AccountContact[contacts.length-1])
: contacts;
}


+ 18
- 0
bubble-server/src/main/java/bubble/model/account/AccountLoginRequest.java 查看文件

@@ -0,0 +1,18 @@
package bubble.model.account;

import org.cobbzilla.wizard.auth.LoginRequest;

import static org.cobbzilla.util.daemon.ZillaRuntime.empty;

public class AccountLoginRequest extends LoginRequest {

public String getEmail () { return getName(); }

public AccountLoginRequest setEmail(String email) {
setName(email);
return this;
}

public boolean hasEmail () { return !empty(getEmail()); }

}

+ 4
- 3
bubble-server/src/main/java/bubble/model/account/AccountPolicy.java 查看文件

@@ -112,13 +112,14 @@ public class AccountPolicy extends IdentifiableBase implements HasAccount {
@Transient public AccountContact[] getAccountContacts () { return accountContactsJson == null ? null : json(accountContactsJson, AccountContact[].class); }
public AccountPolicy setAccountContacts(AccountContact[] contacts) { return setAccountContactsJson(contacts == null ? null : json(contacts, DB_JSON_MAPPER)); }

public AccountPolicy setContact(AccountContact c) { return setContact(c, null, null); }

public AccountPolicy setContact(AccountContact c, Account account, BubbleConfiguration configuration) {
setAccountContacts(AccountContact.set(c, getAccountContacts(), account, configuration));
return this;
}
public AccountPolicy removeContact(AccountContact c) { setAccountContacts(AccountContact.remove(c, getAccountContacts())); return this; }
public AccountPolicy removeContact(Account account, AccountContact c) {
setAccountContacts(AccountContact.remove(account, c, getAccountContacts()));
return this;
}

public List<AccountContact> getAllowedContacts(AccountMessage message) {
if (!hasAccountContacts()) return Collections.emptyList();


+ 6
- 3
bubble-server/src/main/java/bubble/model/account/AccountRegistration.java 查看文件

@@ -17,12 +17,15 @@ public class AccountRegistration extends Account {
@Getter @Setter private String promoCode;
public boolean hasPromoCode () { return !empty(promoCode); }

@Getter @Setter private AccountContact contact;
public boolean hasContact () { return contact != null; }

@Getter @Setter private Boolean agreeToTerms = null;
public boolean agreeToTerms () { return agreeToTerms != null && agreeToTerms; }

@Getter @Setter private Boolean receiveInformationalMessages = null;
public boolean receiveInformationalMessages () { return receiveInformationalMessages != null && receiveInformationalMessages; }

@Getter @Setter private Boolean receivePromotionalMessages = null;
public boolean receivePromotionalMessages () { return receivePromotionalMessages != null && receivePromotionalMessages; }

@Getter @Setter private AccountPaymentMethod paymentMethodObject;
public boolean hasPaymentMethod () { return paymentMethodObject != null; }
}

+ 1
- 1
bubble-server/src/main/java/bubble/model/account/TotpBean.java 查看文件

@@ -18,7 +18,7 @@ public class TotpBean {

public TotpBean(GoogleAuthenticatorKey creds, Account account, BubbleConfiguration configuration) {
setKey(creds.getKey());
setUrl(getOtpAuthTotpURL(configuration.getThisNetwork().getNetworkDomain(), account.getName()+"@"+configuration.getThisNetwork().getNetworkDomain(), creds));
setUrl(getOtpAuthTotpURL(configuration.getThisNetwork().getNetworkDomain(), account.getEmail()+"/"+configuration.getThisNetwork().getNetworkDomain(), creds));
setBackupCodes(creds.getScratchCodes().toArray(new Integer[0]));
}



+ 14
- 2
bubble-server/src/main/java/bubble/model/account/message/handlers/AccountVerifyHandler.java 查看文件

@@ -4,7 +4,9 @@
*/
package bubble.model.account.message.handlers;

import bubble.dao.account.AccountDAO;
import bubble.dao.account.AccountPolicyDAO;
import bubble.model.account.Account;
import bubble.model.account.AccountContact;
import bubble.model.account.AccountPolicy;
import bubble.model.account.message.AccountMessage;
@@ -16,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
public class AccountVerifyHandler implements AccountMessageCompletionHandler {

@Autowired private AccountDAO accountDAO;
@Autowired private AccountPolicyDAO policyDAO;

@Override public void confirm(AccountMessage message, NameAndValue[] data) {
@@ -26,10 +29,19 @@ public class AccountVerifyHandler implements AccountMessageCompletionHandler {
}

@Override public void deny(AccountMessage message) {
final Account account = accountDAO.findByUuid(message.getAccount());
if (account == null) {
log.warn("deny: account not found: "+message.getAccount());
return;
}
final AccountPolicy policy = policyDAO.findSingleByAccount(message.getAccount());
final String contact = message.getRequest().getContact();
final AccountContact contact = policy.findContact(new AccountContact().setUuid(message.getRequest().getContact()));
if (contact == null) {
log.warn("deny: contact not found in policy: "+contact);
return;
}
log.info("deny: removing contact "+ contact +" from account "+message.getAccount());
policy.removeContact(new AccountContact().setUuid(contact));
policy.removeContact(account, contact);
policyDAO.update(policy);
}



+ 6
- 3
bubble-server/src/main/java/bubble/model/boot/ActivationRequest.java 查看文件

@@ -22,9 +22,12 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
@NoArgsConstructor @Accessors(chain=true)
public class ActivationRequest {

@HasValue(message="err.name.required")
@Getter @Setter private String name;
public boolean hasName () { return !empty(name); }
@HasValue(message="err.email.required")
@Getter @Setter private String email;
public boolean hasEmail() { return !empty(email); }

public String getName() { return getEmail(); }
public ActivationRequest setName(String n) { return setEmail(n); }

@HasValue(message="err.password.required")
@Getter @Setter private String password;


+ 9
- 2
bubble-server/src/main/java/bubble/model/cloud/BubbleNetwork.java 查看文件

@@ -201,6 +201,13 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu
return die("hostFromFqdn("+fqdn+"): expected suffix ."+getNetworkDomain());
}

private static final List<String> RESERVED_NAMES = Arrays.asList(
"root", "postmaster", "hostmaster", "webmaster",
"dns", "dnscrypt", "dnscrypt-proxy", "ftp", "www", "www-data", "postgres", "ipfs",
"redis", "nginx", "mitmproxy", "mitmdump", "algo", "algovpn");

public static boolean isReservedName(String name) { return RESERVED_NAMES.contains(name); }

public static HostnameValidationResult validateHostname(HasNetwork request,
AccountDAO accountDAO,
BubbleNetworkDAO networkDAO) {
@@ -211,7 +218,7 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu
final String name = request.getName();
if (!validateRegexMatches(HOST_PART_PATTERN, name)) {
errors.addViolation("err.name.invalid");
} else if (Account.isReservedName(name)) {
} else if (isReservedName(name)) {
errors.addViolation("err.name.reserved");
} else if (name.length() > NETWORK_NAME_MAXLEN) {
errors.addViolation("err.name.length");
@@ -225,7 +232,7 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu
log.info("validateHostname: name "+tryName+" is ineligible (network="+network.getUuid()+") exists");
continue;
} else {
final Account acct = accountDAO.findByName(tryName);
final Account acct = accountDAO.findByEmail(tryName);
if (acct != null && !acct.getUuid().equals(request.getAccount())) {
log.info("validateHostname: name "+tryName+" is ineligible (account="+acct.getUuid()+") exists (request.account="+request.getAccount()+")");
continue;


+ 1
- 1
bubble-server/src/main/java/bubble/resources/IdentityResource.java 查看文件

@@ -55,7 +55,7 @@ public class IdentityResource {
if (caller.admin()) {
// only admin can find any user
found = ((AccountDAO) dao).findById(id);
} else if (id.equals(caller.getUuid()) || id.equals(caller.getName())) {
} else if (id.equals(caller.getUuid()) || id.equals(caller.getEmail())) {
// other callers can find themselves
found = caller;
} else {


+ 13
- 12
bubble-server/src/main/java/bubble/resources/account/AccountsResource.java 查看文件

@@ -46,8 +46,7 @@ import java.util.List;
import java.util.Map;

import static bubble.ApiConstants.*;
import static bubble.model.account.Account.ADMIN_UPDATE_FIELDS;
import static bubble.model.account.Account.validatePassword;
import static bubble.model.account.Account.*;
import static bubble.resources.account.AuthResource.forgotPasswordMessage;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;
@@ -86,22 +85,24 @@ public class AccountsResource {
@Context ContainerRequest ctx,
AccountRegistration request) {

final AccountContext c = new AccountContext(ctx, request.getName(), true);
final AccountContext c = new AccountContext(ctx, request.getEmail(), true);

// only admins can use this endpoint
// regular users must use AuthResource.register
if (!c.caller.admin()) return forbidden();

if (c.account != null) return invalid("err.user.exists", "User with name "+request.getName()+" already exists", request.getName());
if (c.account != null) {
// trying to create self -- just return self. tests sometimes do this
if (c.account.getUuid().equals(c.caller.getUuid())) return ok(c.caller);

// not self, trying to create a user that already exists, return error
return invalid("err.user.exists", "User with name "+request.getEmail()+" already exists", request.getEmail());
}

final ValidationResult errors = new ValidationResult();
final ConstraintViolationBean passwordViolation = validatePassword(request.getPassword());
if (passwordViolation != null) errors.addViolation(passwordViolation);
if (!request.hasContact()) {
errors.addViolation("err.contact.required", "No contact information provided", request.getName());
} else {
request.getContact().validate(errors);
}
errors.addAll(validateEmail(request.getEmail()));
if (!request.agreeToTerms()) {
errors.addViolation("err.terms.required", "You must agree to the legal terms to use this service");
} else {
@@ -283,7 +284,7 @@ public class AccountsResource {
authenticatorService.ensureAuthenticated(ctx, policy, ActionTarget.account);
final AccountContact contact = policy.findContact(new AccountContact().setType(type).setInfo(info));
if (contact == null) return notFound(type.name()+"/"+info);
return ok(policyDAO.update(policy.removeContact(contact)).mask());
return ok(policyDAO.update(policy.removeContact(c.account, contact)).mask());
}

@DELETE @Path("/{id}"+EP_POLICY+EP_CONTACTS+EP_AUTHENTICATOR)
@@ -297,7 +298,7 @@ public class AccountsResource {
final AccountContact contact = policy.findContact(new AccountContact().setType(CloudServiceType.authenticator));
if (contact == null) return notFound(CloudServiceType.authenticator.name());

final AccountPolicy updated = policyDAO.update(policy.removeContact(contact)).mask();
final AccountPolicy updated = policyDAO.update(policy.removeContact(c.account, contact)).mask();
authenticatorService.flush(c.caller.getToken());

return ok(updated);
@@ -313,7 +314,7 @@ public class AccountsResource {

final AccountContact found = policy.findContact(new AccountContact().setUuid(uuid));
if (found == null) return notFound(uuid);
return ok(policyDAO.update(policy.removeContact(found)).mask());
return ok(policyDAO.update(policy.removeContact(c.account, found)).mask());
}

@DELETE @Path("/{id}"+EP_REQUEST)


+ 14
- 21
bubble-server/src/main/java/bubble/resources/account/AuthResource.java 查看文件

@@ -37,7 +37,6 @@ import bubble.service.cloud.DeviceIdService;
import bubble.service.notify.NotificationService;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.wizard.auth.LoginRequest;
import org.cobbzilla.wizard.stream.FileSendableResource;
import org.cobbzilla.wizard.validation.ConstraintViolationBean;
import org.cobbzilla.wizard.validation.SimpleViolationException;
@@ -133,7 +132,7 @@ public class AuthResource {
if (accountDAO.activated()) {
return invalid("err.activation.alreadyDone", "activation has already been done");
}
if (!request.hasName()) return invalid("err.name.required", "name 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.getPassword().contains("{{") && request.getPassword().contains("}}")) {
@@ -212,21 +211,15 @@ public class AuthResource {

request.setAdmin(false); // cannot register admins, they must be created

final ValidationResult errors = request.validateName();
final ValidationResult errors = request.validateEmail();
if (errors.isValid()) {
final Account existing = accountDAO.findByName(request.getName());
if (existing != null) errors.addViolation("err.name.registered", "Name is already registered: ", request.getName());
final Account existing = accountDAO.findByEmail(request.getEmail());
if (existing != null) errors.addViolation("err.name.registered", "Name is already registered: ", request.getEmail());
}

final ConstraintViolationBean passwordViolation = validatePassword(request.getPassword());
if (passwordViolation != null) errors.addViolation(passwordViolation);

if (!request.hasContact()) {
errors.addViolation("err.contact.required", "No contact information provided", request.getName());
} else {
request.getContact().validate(errors);
}

if (!request.agreeToTerms()) {
errors.addViolation("err.terms.required", "You must agree to the legal terms to use this service");
} else {
@@ -301,21 +294,21 @@ public class AuthResource {
@POST @Path(EP_LOGIN)
public Response login(@Context Request req,
@Context ContainerRequest ctx,
LoginRequest request,
AccountLoginRequest request,
@QueryParam("k") String unlockKey) {
if (!request.hasName()) return invalid("err.name.required", "name is required");
if (!request.hasEmail()) return invalid("err.email.required", "email is required");
if (!request.hasPassword()) return invalid("err.password.required", "password is required");
final Account account = accountDAO.findByName(request.getName());
if (account == null || account.deleted()) return notFound(request.getName());
final Account account = accountDAO.findByEmail(request.getEmail());
if (account == null || account.deleted()) return notFound(request.getEmail());
if (!account.getHashedPassword().isCorrectPassword(request.getPassword())) {
return notFound(request.getName());
return notFound(request.getEmail());
}
if (account.suspended()) return invalid("err.account.suspended");

boolean isUnlock = false;
if (account.locked()) {
if (!accountDAO.locked()) {
log.info("login: account "+account.getName()+" was locked, but system is unlocked, unlocking again");
log.info("login: account "+account.getEmail()+" was locked, but system is unlocked, unlocking again");
accountDAO.unlock();

} else {
@@ -360,7 +353,7 @@ public class AuthResource {
);
}
return ok(new Account()
.setName(account.getName())
.setEmail(account.getEmail())
.setLoginRequest(loginRequest != null ? loginRequest.getUuid() : null)
.setMultifactorAuth(AccountContact.mask(authFactors)));
}
@@ -387,9 +380,9 @@ public class AuthResource {
@POST @Path(EP_FORGOT_PASSWORD)
public Response forgotPassword(@Context Request req,
@Context ContainerRequest ctx,
LoginRequest request) {
if (!request.hasName()) return invalid("err.name.required");
final Account account = accountDAO.findById(request.getName());
AccountLoginRequest request) {
if (!request.hasEmail()) return invalid("err.email.required");
final Account account = accountDAO.findById(request.getEmail());
if (account == null) return ok();

accountMessageDAO.create(forgotPasswordMessage(req, account, configuration));


+ 2
- 2
bubble-server/src/main/java/bubble/resources/message/MessagesResource.java 查看文件

@@ -103,9 +103,9 @@ public class MessagesResource {
if (!messageCache.containsKey(cacheKey)) {
final Properties props;
if (isAppsGroup) {
if (log.isDebugEnabled()) log.debug("loadMessages: loading app messages for caller="+caller.getName()+", locale="+locale);
if (log.isDebugEnabled()) log.debug("loadMessages: loading app messages for caller="+caller.getEmail()+", locale="+locale);
props = appMessageService.loadAppMessages(caller, locale);
if (log.isDebugEnabled()) log.debug("loadMessages: loaded app messages for caller="+caller.getName()+", locale="+locale+", props.size="+props.size());
if (log.isDebugEnabled()) log.debug("loadMessages: loaded app messages for caller="+caller.getEmail()+", locale="+locale+", props.size="+props.size());
} else {
props = new Properties();
props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs)));


+ 3
- 3
bubble-server/src/main/java/bubble/rule/TrafficRecord.java 查看文件

@@ -2,7 +2,7 @@
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.rule.analytics;
package bubble.rule;

import bubble.model.account.Account;
import bubble.model.device.Device;
@@ -22,7 +22,7 @@ public class TrafficRecord {
@Getter @Setter private long requestTime = now();
@Getter @Setter private String action;
@Getter @Setter private String accountUuid;
@Getter @Setter private String accountName;
@Getter @Setter private String accountEmail;
@Getter @Setter private String deviceUuid;
@Getter @Setter private String deviceName;
@Getter @Setter private String ip;
@@ -32,7 +32,7 @@ public class TrafficRecord {
@Getter @Setter private String referer;

public TrafficRecord(FilterMatchersRequest filter, Account account, Device device) {
setAccountName(account == null ? null : account.getName());
setAccountEmail(account == null ? null : account.getEmail());
setAccountUuid(account == null ? null : account.getUuid());
setDeviceName(device == null ? null : device.getName());
setDeviceUuid(device == null ? null : device.getUuid());


+ 1
- 0
bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalyticsRuleDriver.java 查看文件

@@ -10,6 +10,7 @@ import bubble.model.device.Device;
import bubble.resources.stream.FilterMatchersRequest;
import bubble.rule.AbstractAppRuleDriver;
import bubble.rule.FilterMatchDecision;
import bubble.rule.TrafficRecord;
import bubble.service.stream.AppRuleHarness;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;


+ 1
- 1
bubble-server/src/main/java/bubble/server/listener/BubbleFirstTimeListener.java 查看文件

@@ -64,7 +64,7 @@ public class BubbleFirstTimeListener extends RestServerLifecycleListenerBase<Bub
}
final AccountPolicy adminPolicy = configuration.getBean(AccountPolicyDAO.class).findSingleByAccount(adminAccount.getUuid());
if (adminPolicy == null || !adminPolicy.hasVerifiedNonAuthenticatorAccountContacts()) {
log.error("onStart: no AccountPolicy found (or no verified non-authenticator contacts) for admin account (" + adminAccount.getName() + "), cannot send first time install message, unlocking now");
log.error("onStart: no AccountPolicy found (or no verified non-authenticator contacts) for admin account (" + adminAccount.getEmail() + "), cannot send first time install message, unlocking now");
accountDAO.unlock();
return;
}


+ 1
- 1
bubble-server/src/main/java/bubble/server/listener/DeviceInitializerListener.java 查看文件

@@ -24,7 +24,7 @@ public class DeviceInitializerListener extends RestServerLifecycleListenerBase {

if (!configuration.isSageLauncher()) {
for (Account a : accountDAO.findAll()) {
if (!a.getName().equals(ROOT_USERNAME)) {
if (!a.getEmail().equals(ROOT_USERNAME)) {
deviceDAO.ensureSpareDevice(a.getUuid(), thisNode.getNetwork(), false);
}
}


+ 1
- 1
bubble-server/src/main/java/bubble/service/account/StandardSyncPasswordService.java 查看文件

@@ -63,7 +63,7 @@ public class StandardSyncPasswordService implements SyncPasswordService {
notificationService.notify(configuration.getSageNode(), sync_password, notification);

} else {
reportError("syncPassword("+account.getName()+"/"+account.getUuid()+"): invalid installType: "+installType);
reportError("syncPassword("+account.getEmail()+"/"+account.getUuid()+"): invalid installType: "+installType);
}
}



+ 1
- 1
bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties 查看文件

@@ -554,7 +554,6 @@ err.cloudServiceType.required=Cloud type is required
err.cloudServiceType.invalid=Cloud type is invalid
err.cloudType.invalid=Cloud type is invalid
err.configJson.length=Configuration JSON is too long
err.contact.required=Contact information is required
err.contactType.required=Contact type is required
err.contact.authenticatorAuthFactorCannotBeChanged=Authenticator is always a required auth factor
err.contact.unverified=Cannot configure an unverified contact; verify first
@@ -657,6 +656,7 @@ err.paymentService.notFound=Payment service is invalid
err.parent.notFound=Parent account does not exist
err.path.length=Path is too long
err.plan.required=Plan is required
err.plan.planMaxAccountLimit=No more accounts can be created. Please upgrade your plan to create more accounts.
err.price.invalid=Price is invalid
err.price.length=Price is too long
err.privateKeyHash.length=Private key hash is too long


+ 2
- 1
bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties 查看文件

@@ -198,9 +198,10 @@ err.registration.disabled=Account registration is not enabled on this Bubble
err.register.alreadyLoggedIn=Cannot register a new account when logged in
err.name.registered=Username is already registered
err.contactType.required=Contact type is required
err.contact.required=No contact information provided
err.email.required=Email is required
err.email.invalid=Email is invalid
err.email.tooLong=Email is too long
err.email.registered=Email is already registered
err.phone.required=SMS Phone is required
err.phone.invalid=SMS Phone is invalid
err.phone.length=SMS Phone is too long


+ 1
- 1
bubble-server/src/test/java/bubble/test/ActivatedBubbleModelTestBase.java 查看文件

@@ -116,7 +116,7 @@ public abstract class ActivatedBubbleModelTestBase extends BubbleModelTestBase {
// if DB already exists, server has already been activated
try {
admin = client.put(AUTH_ENDPOINT + EP_ACTIVATE, new ActivationRequest()
.setName(ROOT_USERNAME)
.setEmail(ROOT_USERNAME)
.setPassword(ROOT_PASSWORD)
.setNetworkName(hostname_short())
.addCloudConfig(dns)


+ 4
- 6
bubble-server/src/test/resources/models/include/new_account.json 查看文件

@@ -3,7 +3,6 @@
"comment": "declare default parameters for new_account test part",
"include": "_defaults",
"params": {
"username": "user-{{rand 10}}",
"password": "foobar1!",
"email": "user-{{rand 5}}@example.com",
"rootSessionName": "rootSession",
@@ -21,11 +20,10 @@
"uri": "users",
"method": "put",
"entity": {
"name": "<<username>>",
"email": "<<email>>",
"password": "<<password>>",
"admin": "<<admin>>",
"agreeToTerms": true,
"contact": {"type": "email", "info": "<<email>>"}
"agreeToTerms": true
}
},
"response": {
@@ -56,7 +54,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "<<username>>",
"name": "<<email>>",
"password": "<<password>>"
}
},
@@ -96,7 +94,7 @@
"request": {
"session": "new",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "<<username>>"}]
"entity": [{"name": "account", "value": "<<email>>"}]
}
},



+ 1
- 2
bubble-server/src/test/resources/models/include/referral_signup.json 查看文件

@@ -19,10 +19,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "<<referredName>>",
"name": "<<referredName>>@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "<<referredName>>@example.com"},
"promoCode": "<<referralCode>>"
}
},


+ 1
- 1
bubble-server/src/test/resources/models/system/account_testDevice.json 查看文件

@@ -1,6 +1,6 @@
[
{
"name": "root",
"email": "root",
"children": {
"Device": [ {"name": "test"} ]
}


+ 0
- 2
bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account.json 查看文件

@@ -3,8 +3,6 @@
"comment": "activate service, create account, login for block_delete",
"include": "new_account",
"params": {
"username": "user-to-partially-delete",
"password": "foobar1!",
"email": "user-partially-account-deletion@example.com",
"verifyEmail": "true"
}


+ 2
- 3
bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account_with_payments.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "user_with_payment_to_block_delete",
"name": "user_with_payment_to_block_delete@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": { "type": "email", "info": "user_with_payment_to_block_delete@example.com" }
"agreeToTerms": true
}
},
"response": { "store": "testAccount" }


+ 1
- 3
bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account.json 查看文件

@@ -3,8 +3,6 @@
"comment": "activate service, create account, login",
"include": "new_account",
"params": {
"username": "user-to-delete",
"password": "foobar1!",
"email": "user-account-deletion@example.com",
"verifyEmail": "true"
}
@@ -42,7 +40,7 @@
{
"comment": "lookup user - just checking it is the one for deletion",
"request": { "uri": "users/{{user.uuid}}" },
"response": { "check": [{ "condition": "json.getName() == 'user-to-delete'" }] }
"response": { "check": [{ "condition": "json.getName() == 'user-account-deletion@example.com'" }] }
},

{


+ 2
- 3
bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account_with_payments.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "user_with_payment_to_delete",
"name": "user_with_payment_to_delete@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": { "type": "email", "info": "user_with_payment_to_delete@example.com" }
"agreeToTerms": true
}
},
"response": { "store": "testAccount" }


+ 0
- 1
bubble-server/src/test/resources/models/tests/auth/account_crud.json 查看文件

@@ -3,7 +3,6 @@
"comment": "activate service, create account, login",
"include": "new_account",
"params": {
"password": "foobar1!",
"email": "user-account-crud@example.com",
"verifyEmail": "true"
}


+ 14
- 17
bubble-server/src/test/resources/models/tests/auth/account_registration.json 查看文件

@@ -14,9 +14,8 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "foobar1",
"password": "password1!",
"contact": { "type": "email", "info": "user-{{rand 5}}@example.com" }
"name": "account_reg_user@example.com",
"password": "password1!"
}
},
"response": {
@@ -31,10 +30,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "foobar1",
"name": "account_reg_user@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": { "type": "email", "info": "user-{{rand 5}}@example.com" }
"agreeToTerms": true
}
},
"response": {
@@ -47,7 +45,7 @@
"comment": "view current user",
"request": { "uri": "me" },
"response": {
"check": [ {"condition": "json.getName() == 'foobar1'"} ]
"check": [ {"condition": "json.getName() == 'account_reg_user@example.com'"} ]
}
},

@@ -60,12 +58,12 @@
},

{
"comment": "login as foobar1, works",
"comment": "login as account_reg_user@example.com, works",
"request": {
"session": "new",
"uri": "auth/login",
"entity": {
"name": "foobar1",
"name": "account_reg_user@example.com",
"password": "password1!"
}
},
@@ -80,7 +78,7 @@
"request": { "uri": "me" },
"response": {
"store": "user1",
"check": [ {"condition": "json.getName() == 'foobar1'"} ]
"check": [ {"condition": "json.getName() == 'account_reg_user@example.com'"} ]
}
},

@@ -158,7 +156,7 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "foobar1",
"name": "account_reg_user@example.com",
"password": "password1!",
"agreeToTerms": true
}
@@ -176,7 +174,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "foobar1",
"name": "account_reg_user@example.com",
"password": "password1!"
}
},
@@ -202,7 +200,7 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "foobar1",
"name": "account_reg_user@example.com",
"password": "password1!",
"agreeToTerms": true
}
@@ -219,10 +217,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "foobar2",
"name": "another_account_reg_user@example.com",
"password": "password2!",
"agreeToTerms": true,
"contact": { "type": "email", "info": "user2-{{rand 5}}@example.com" }
"agreeToTerms": true
}
},
"response": {
@@ -235,7 +232,7 @@
"comment": "view new user",
"request": { "uri": "me" },
"response": {
"check": [ {"condition": "json.getName() == 'foobar2'"} ]
"check": [ {"condition": "json.getName() == 'another_account_reg_user@example.com'"} ]
}
}
]

+ 2
- 2
bubble-server/src/test/resources/models/tests/auth/basic_auth.json 查看文件

@@ -6,7 +6,7 @@
"method": "put",
"uri": "auth/activate",
"entity": {
"name": "root2",
"email": "root2",
"password": "pass123!@x"
}
},
@@ -39,7 +39,7 @@
"comment": "read self",
"request": { "uri": "me" },
"response": {
"check": [ {"condition": "json.getName() == 'root'"} ]
"check": [ {"condition": "json.getEmail() == 'root'"} ]
}
},



+ 6
- 7
bubble-server/src/test/resources/models/tests/auth/change_admin_password.json 查看文件

@@ -21,7 +21,6 @@
"comment": "as root user, create another admin user",
"include": "new_account",
"params": {
"username": "admin-change_password",
"email": "admin-change_password@example.com",
"password": "bazquux1!",
"admin": true,
@@ -35,7 +34,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "admin-change_password",
"name": "admin-change_password@example.com",
"password": "bazquux1!"
}
},
@@ -50,7 +49,7 @@
"request": { "uri": "me" },
"response": {
"check": [
{"condition": "json.getName() === 'admin-change_password'"},
{"condition": "json.getName() === 'admin-change_password@example.com'"},
{"condition": "json.admin() === true"}
]
}
@@ -150,7 +149,7 @@
{
"comment": "as root user, try to change admin user password without sending current password, succeeds because we are senior admin",
"request": {
"uri": "users/admin-change_password/changePassword",
"uri": "users/admin-change_password@example.com/changePassword",
"entity": {
"newPassword": "newadminPASS1!"
}
@@ -163,7 +162,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "admin-change_password",
"name": "admin-change_password@example.com",
"password": "bazquux1!"
}
},
@@ -178,7 +177,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "admin-change_password",
"name": "admin-change_password@example.com",
"password": "newadminPASS1!"
}
},
@@ -192,7 +191,7 @@
"comment": "as second admin, read self, succeeds",
"request": { "uri": "me" },
"response": {
"check": [ {"condition": "json.getName() === 'admin-change_password'"} ]
"check": [ {"condition": "json.getName() === 'admin-change_password@example.com'"} ]
}
}


+ 12
- 14
bubble-server/src/test/resources/models/tests/auth/change_password.json 查看文件

@@ -140,9 +140,7 @@
"comment": "create a non-admin user",
"include": "new_account",
"params": {
"username": "user-change_password",
"email": "user-change_password@example.com",
"password": "foobar1!",
"verifyEmail": "true"
}
},
@@ -153,7 +151,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "user-change_password",
"name": "user-change_password@example.com",
"password": "foobar1!"
}
},
@@ -167,14 +165,14 @@
"comment": "as non-admin, read self-profile, succeeds",
"request": { "uri": "me" },
"response": {
"check": [ {"condition": "json.getName() === 'user-change_password'"} ]
"check": [ {"condition": "json.getName() === 'user-change_password@example.com'"} ]
}
},

{
"comment": "as root, change non-admin user password, do not need to send old password, invalidates all sessions",
"request": {
"uri": "users/user-change_password/changePassword",
"uri": "users/user-change_password@example.com/changePassword",
"session": "rootSession",
"entity": {
"newPassword": "newuserpass1!"
@@ -199,7 +197,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "user-change_password",
"name": "user-change_password@example.com",
"password": "foobar1!"
}
},
@@ -214,7 +212,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "user-change_password",
"name": "user-change_password@example.com",
"password": "newuserpass1!"
}
},
@@ -226,7 +224,7 @@

{
"comment": "as non-admin, read self policy, succeeds",
"request": { "uri": "users/user-change_password/policy" },
"request": { "uri": "users/user-change_password@example.com/policy" },
"response": { "store": "userPolicy" }
},

@@ -284,7 +282,7 @@
"uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}",
"method": "post",
"entity": [
{ "name": "account", "value": "user-change_password" },
{ "name": "account", "value": "user-change_password@example.com" },
{ "name": "password", "value": "new_password3!" }
]
}
@@ -296,7 +294,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "user-change_password",
"name": "user-change_password@example.com",
"password": "new_password3!"
}
},
@@ -309,7 +307,7 @@
{
"comment": "as non-admin user, update profile, set email as not required for account operations",
"request": {
"uri": "users/user-change_password/policy/contacts",
"uri": "users/user-change_password@example.com/policy/contacts",
"entity": {
"type": "email",
"info": "user-change_password@example.com",
@@ -343,7 +341,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "user-change_password",
"name": "user-change_password@example.com",
"password": "new_password4!"
}
},
@@ -357,14 +355,14 @@
"comment": "update user policy, add authenticator",
"include": "add_authenticator",
"params": {
"userId": "user-change_password",
"userId": "user-change_password@example.com",
"authenticatorVar": "userAuthenticator"
}
},

{
"comment": "re-read user policy, verify authenticator is required for account operations",
"request": { "uri": "users/user-change_password/policy" },
"request": { "uri": "users/user-change_password@example.com/policy" },
"response": {
"store": "userPolicy",
"check": [


+ 1
- 5
bubble-server/src/test/resources/models/tests/auth/device_crud.json 查看文件

@@ -1,11 +1,7 @@
[
{
"comment": "create an account",
"include": "new_account",
"params": {
"name": "test-user-{{rand 5}}",
"password": "password1!"
}
"include": "new_account"
},

{


+ 3
- 5
bubble-server/src/test/resources/models/tests/auth/download_account.json 查看文件

@@ -3,9 +3,7 @@
"comment": "activate service, create account, login",
"include": "new_account",
"params": {
"username": "testuser",
"password": "foobar1!",
"email": "testuser@example.com",
"email": "download_account_user@example.com",
"verifyEmail": "true",
"userSessionName": "userSession"
}
@@ -33,7 +31,7 @@
"comment": "as root, check email inbox for download request message",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/testuser@example.com?type=request&action=download"
"uri": "debug/inbox/email/download_account_user@example.com?type=request&action=download"
},
"response": {
"store": "emailInbox",
@@ -59,7 +57,7 @@
"comment": "as root, check email inbox for download confirmation",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/testuser@example.com?type=confirmation&action=download"
"uri": "debug/inbox/email/download_account_user@example.com?type=confirmation&action=download"
},
"response": {
"store": "emailInbox",


+ 6
- 8
bubble-server/src/test/resources/models/tests/auth/forgot_password.json 查看文件

@@ -1,11 +1,9 @@
[
{
"comment": "activate service, create account, login",
"comment": "create account, login",
"include": "new_account",
"params": {
"username": "user-forgot_password",
"email": "user-forgot_password@example.com",
"password": "foobar1!",
"verifyEmail": "true"
}
},
@@ -62,7 +60,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-forgot_password"}]
"entity": [{"name": "account", "value": "user-forgot_password@example.com"}]
}
},

@@ -120,7 +118,7 @@
"session": "new",
"uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}",
"entity": [
{ "name": "account", "value": "user-forgot_password" },
{ "name": "account", "value": "user-forgot_password@example.com" },
{ "name": "password", "value": "new_password" }
]
},
@@ -136,7 +134,7 @@
"session": "new",
"uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}",
"entity": [
{ "name": "account", "value": "user-forgot_password" },
{ "name": "account", "value": "user-forgot_password@example.com" },
{ "name": "password", "value": "try_new_password1!" }
]
},
@@ -155,7 +153,7 @@
"session": "new",
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}",
"entity": [
{ "name": "account", "value": "user-forgot_password" },
{ "name": "account", "value": "user-forgot_password@example.com" },
{ "name": "password", "value": "new_password1!" }
]
}
@@ -167,7 +165,7 @@
"session": "new",
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}",
"entity": [
{ "name": "account", "value": "user-forgot_password" },
{ "name": "account", "value": "user-forgot_password@example.com" },
{ "name": "password", "value": "new_password2!" }
]
},


+ 10
- 12
bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json 查看文件

@@ -3,9 +3,7 @@
"comment": "create new account and login",
"include": "new_account",
"params": {
"username": "user-multifactor_auth",
"email": "user-multifactor_auth@example.com",
"password": "foobar1!"
"email": "user-multifactor_auth@example.com"
}
},

@@ -74,7 +72,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
}
},

@@ -172,7 +170,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
},
"response": {
"sessionName": "userSession",
@@ -250,7 +248,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
},
"response": {
"status": 422,
@@ -264,7 +262,7 @@
"session": "userSession",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [
{"name": "account", "value": "user-multifactor_auth"},
{"name": "account", "value": "user-multifactor_auth@example.com"},
{"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"}
]
},
@@ -374,7 +372,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
}
},

@@ -461,7 +459,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
},
"response": {
"status": 422,
@@ -475,7 +473,7 @@
"session": "userSession",
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}",
"entity": [
{"name": "account", "value": "user-multifactor_auth"},
{"name": "account", "value": "user-multifactor_auth@example.com"},
{"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"}
]
},
@@ -552,7 +550,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
}
},

@@ -621,7 +619,7 @@
"request": {
"session": "userSession",
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}",
"entity": [{"name": "account", "value": "user-multifactor_auth"}]
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}]
},
"response": {
"sessionName": "userSession",


+ 4
- 5
bubble-server/src/test/resources/models/tests/live/backup_and_restore.json 查看文件

@@ -89,10 +89,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "user1",
"name": "backup_and_restore_user@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "user-{{rand 5}}@example.com"}
"agreeToTerms": true
}
}
},
@@ -100,7 +99,7 @@
{
"before": "sleep 24s",
"comment": "lookup policy for new account",
"request": { "uri": "users/user1/policy" },
"request": { "uri": "users/backup_and_restore_user/policy" },
"response": { "store": "user1policy" }
},

@@ -212,7 +211,7 @@
{
"comment": "verify account we added has been restored",
"request": {
"uri": "users/user1/policy"
"uri": "users/backup_and_restore_user/policy"
},
"response": {
"check": [ {"condition": "json.getFirstEmail() == user1policy.getFirstEmail()"} ]


+ 6
- 8
bubble-server/src/test/resources/models/tests/network/network_regions.json 查看文件

@@ -14,11 +14,10 @@
"comment": "list all available compute regions",
"request": { "uri": "me/regions" },
"response": {
"store": "regions",
"check": [
// ensure we have at least one region from each cloud
{"condition": "_find(json, function (r) { return r.getCloud() == computeClouds[0].getUuid(); }) != null"},
{"condition": "_find(json, function (r) { return r.getCloud() == computeClouds[1].getUuid(); }) != null"},
{"condition": "_find(json, function (r) { return r.getCloud() == computeClouds[2].getUuid(); }) != null"}
// ensure we have several regions
{"condition": "json.length > 5"}
]
}
},
@@ -28,10 +27,9 @@
"request": { "uri": "me/regions/closest" },
"response": {
"check": [
// ensure we have at least one region from each cloud
{"condition": "_find(json, function (r) { return r.getCloud() == computeClouds[0].getUuid(); }) != null"},
{"condition": "_find(json, function (r) { return r.getCloud() == computeClouds[1].getUuid(); }) != null"},
{"condition": "_find(json, function (r) { return r.getCloud() == computeClouds[2].getUuid(); }) != null"}
// ensure we have the same number of regions and that first is closer than last
{"condition": "json.length === regions.length"},
{"condition": "json[0].getDistance() < json[json.length-1].getDistance()"}
]
}
}

+ 4
- 6
bubble-server/src/test/resources/models/tests/network/simple_network.json 查看文件

@@ -13,10 +13,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_simple_net",
"name": "test_user_simple_net@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
},
"response": {
@@ -341,10 +340,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "bubble_user",
"name": "bubble_user@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "bubble-user@example.com"}
"agreeToTerms": true
}
}
},


+ 3
- 4
bubble-server/src/test/resources/models/tests/payment/pay_code.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_code",
"name": "test_user_code@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_user_code",
"name": "test_user_code@example.com",
"password": "password1!"
}
},


+ 4
- 5
bubble-server/src/test/resources/models/tests/payment/pay_credit.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_credit",
"name": "test_user_credit@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_user_credit",
"name": "test_user_credit@example.com",
"password": "password1!"
}
},
@@ -106,7 +105,7 @@
"comment": "as root, check email inbox for verification message",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/test-user@example.com?type=request&action=verify&target=account"
"uri": "debug/inbox/email/test_user_credit@example.com?type=request&action=verify&target=account"
},
"response": {
"store": "emailInbox",


+ 5
- 6
bubble-server/src/test/resources/models/tests/payment/pay_credit_refund_and_restart.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_credit_refund_restart",
"name": "test_user_credit_refund_restart@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_user_credit_refund_restart",
"name": "test_user_credit_refund_restart@example.com",
"password": "password1!"
}
},
@@ -45,7 +44,7 @@
"comment": "as root, check email inbox for verification message",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/test-user@example.com?type=request&action=verify&target=account"
"uri": "debug/inbox/email/test_user_credit_refund_restart@example.com?type=request&action=verify&target=account"
},
"response": {
"store": "emailInbox",
@@ -198,7 +197,7 @@
{"condition": "json[0].getAmount() < {{bills.[0].total}}"},
// not sure if current month has 28, 29, 30 or 31 days, so let's make some reasonable bounds
// we should only have a refund for all but one day
{"condition": "json[0].getAmount() > Math.round(bills[0].getTotal() - (bills[0].getTotal() / (bills[0].daysInPeriod() - 1)))"},
{"condition": "json[0].getAmount() >= Math.round((bills[0].daysInPeriod() - 1) * (bills[0].getTotal() / bills[0].daysInPeriod()))"},
{"condition": "json[0].getAmount() <= Math.round(bills[0].getTotal() - (bills[0].getTotal() / bills[0].daysInPeriod()))"}
]
}


+ 3
- 4
bubble-server/src/test/resources/models/tests/payment/pay_free.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_free",
"name": "test_user_free@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_user_free",
"name": "test_user_free@example.com",
"password": "password1!"
}
},


+ 3
- 4
bubble-server/src/test/resources/models/tests/payment/plan_apps.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_plan_apps_user",
"name": "test_plan_apps_user@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_plan_apps_user",
"name": "test_plan_apps_user@example.com",
"password": "password1!"
}
},


+ 6
- 7
bubble-server/src/test/resources/models/tests/payment/recurring_billing.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_recurring",
"name": "test_user_recurring@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test-user@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_user_recurring",
"name": "test_user_recurring@example.com",
"password": "password1!"
}
},
@@ -45,7 +44,7 @@
"comment": "as root, check email inbox for verification message",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/test-user@example.com?type=request&action=verify&target=account"
"uri": "debug/inbox/email/test_user_recurring@example.com?type=request&action=verify&target=account"
},
"response": {
"store": "emailInbox",
@@ -346,7 +345,7 @@
"comment": "as root, verify payment reminder message has been sent",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/test-user@example.com?type=request&action=payment&target=network"
"uri": "debug/inbox/email/test_user_recurring@example.com?type=request&action=payment&target=network"
},
"response": {
"store": "emailInbox",
@@ -380,7 +379,7 @@
"comment": "as root, verify nonpayment message has been sent",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/test-user@example.com?type=notice&action=payment&target=network"
"uri": "debug/inbox/email/test_user_recurring@example.com?type=notice&action=payment&target=network"
},
"response": {
"store": "emailInbox",


+ 12
- 13
bubble-server/src/test/resources/models/tests/promo/account_credit.json 查看文件

@@ -5,10 +5,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "test_user_small_promo",
"name": "account_credit_user@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_small_promo@example.com"}
"agreeToTerms": true
}
},
"response": {
@@ -23,7 +22,7 @@
"comment": "root: check email inbox for verification message",
"request": {
"session": "rootSession",
"uri": "debug/inbox/email/test_user_small_promo@example.com?type=request&action=verify&target=account"
"uri": "debug/inbox/email/account_credit_user@example.com?type=request&action=verify&target=account"
},
"response": {
"store": "emailInbox",
@@ -83,7 +82,7 @@
{
"comment": "root: apply AccountCredit5 promotion to account",
"request": {
"uri": "users/test_user_small_promo/promos",
"uri": "users/account_credit_user@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"
@@ -93,7 +92,7 @@

{
"comment": "root: list promos available, should be credit",
"request": {"uri": "users/test_user_small_promo/promos"},
"request": {"uri": "users/account_credit_user@example.com/promos"},
"response": {
"check": [
{"condition": "json.length === 1"},
@@ -119,7 +118,7 @@
{
"comment": "user: try to add second AccountCredit5, admin only",
"request": {
"uri": "users/test_user_small_promo/promos",
"uri": "users/account_credit_user@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"
@@ -132,7 +131,7 @@
"comment": "root: add second AccountCredit5 promotion to account",
"request": {
"session": "rootSession",
"uri": "users/test_user_small_promo/promos",
"uri": "users/account_credit_user@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"
@@ -143,7 +142,7 @@
{
"comment": "root: add third AccountCredit5 promotion to account",
"request": {
"uri": "users/test_user_small_promo/promos",
"uri": "users/account_credit_user@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"
@@ -154,7 +153,7 @@
{
"comment": "root: add fourth AccountCredit5 promotion to account",
"request": {
"uri": "users/test_user_small_promo/promos",
"uri": "users/account_credit_user@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"
@@ -167,7 +166,7 @@

{
"comment": "root: list promos available, should be four credits",
"request": {"uri": "users/test_user_small_promo/promos"},
"request": {"uri": "users/account_credit_user@example.com/promos"},
"response": {
"check": [
{"condition": "json.length === 4"},
@@ -200,7 +199,7 @@
"comment": "root: remove last AccountCredit5 promotion",
"request": {
"session": "rootSession",
"uri": "users/test_user_small_promo/promos/{{promos.[0].uuid}}",
"uri": "users/account_credit_user@example.com/promos/{{promos.[0].uuid}}",
"method": "delete"
},
"response": {
@@ -475,7 +474,7 @@
"comment": "root: apply another AccountCredit5 promotion to account",
"request": {
"session": "rootSession",
"uri": "users/test_user_small_promo/promos",
"uri": "users/account_credit_user@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"


+ 2
- 3
bubble-server/src/test/resources/models/tests/promo/first_month_free.json 查看文件

@@ -5,10 +5,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "test_user_1mo_free",
"name": "test_user_1mo_free@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_1mo_free@example.com"}
"agreeToTerms": true
}
},
"response": {


+ 4
- 5
bubble-server/src/test/resources/models/tests/promo/multi_promo.json 查看文件

@@ -5,10 +5,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "test_user_referring_multi",
"name": "test_user_referring_multi@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_referring_multi@example.com"}
"agreeToTerms": true
}
},
"response": {
@@ -66,7 +65,7 @@
{
"comment": "as root, grant some referral codes to the referring user",
"request": {
"uri": "users/test_user_referring_multi/referralCodes",
"uri": "users/test_user_referring_multi@example.com/referralCodes",
"method": "put",
"entity": { "count": 3 }
},
@@ -484,7 +483,7 @@
"comment": "root: apply another AccountCredit5 promotion to referring account",
"request": {
"session": "rootSession",
"uri": "users/test_user_referring_multi/promos",
"uri": "users/test_user_referring_multi@example.com/promos",
"method": "put",
"entity": {
"name": "AccountCredit5"


+ 5
- 7
bubble-server/src/test/resources/models/tests/promo/referral_month_free.json 查看文件

@@ -5,10 +5,9 @@
"uri": "users",
"method": "put",
"entity": {
"name": "test_user_referring_free",
"name": "test_user_referring_free@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_referring_free@example.com"}
"agreeToTerms": true
}
}
},
@@ -20,7 +19,7 @@
"session": "new",
"uri": "auth/login",
"entity": {
"name": "test_user_referring_free",
"name": "test_user_referring_free@example.com",
"password": "password1!"
}
},
@@ -61,7 +60,7 @@
{
"comment": "as root, grant some referral codes to the referring user",
"request": {
"uri": "users/test_user_referring_free/referralCodes",
"uri": "users/test_user_referring_free@example.com/referralCodes",
"method": "put",
"entity": { "count": 3 }
},
@@ -102,10 +101,9 @@
"session": "new",
"uri": "auth/register",
"entity": {
"name": "test_user_referred_free",
"name": "test_user_referred_free@example.com",
"password": "password1!",
"agreeToTerms": true,
"contact": {"type": "email", "info": "test_user_referred_free@example.com"},
"promoCode": "{{referralCodes.[0].name}}"
}
},


+ 1
- 1
bubble-web

@@ -1 +1 @@
Subproject commit 4945a14a640935ebebc07811d7e7168b12a0b180
Subproject commit e3a90cb72319d18639bdfda173bc8fde200609a7

+ 1
- 1
utils/cobbzilla-wizard

@@ -1 +1 @@
Subproject commit a07578cf1fde1cdaee0abc626fa4761fe8421446
Subproject commit 3b1649f05991edaf82d117ecf3080e46a16b63b4

Loading…
取消
儲存