# Conflicts: # bubble-server/src/main/java/bubble/server/BubbleConfiguration.java # bubble-webpull/20/head
@@ -11,6 +11,7 @@ import com.warrenstrange.googleauth.GoogleAuthenticator; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.apache.commons.lang3.RandomUtils; | import org.apache.commons.lang3.RandomUtils; | ||||
import org.cobbzilla.util.daemon.ZillaRuntime; | |||||
import org.cobbzilla.util.io.FileUtil; | import org.cobbzilla.util.io.FileUtil; | ||||
import org.glassfish.grizzly.http.server.Request; | import org.glassfish.grizzly.http.server.Request; | ||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
@@ -232,7 +233,10 @@ public class ApiConstants { | |||||
return val == null ? null : val.textValue(); | return val == null ? null : val.textValue(); | ||||
} | } | ||||
@Getter(lazy=true) private static final String[] hostPrefixes = stream2string("bubble/host-prefixes.txt").split("\n"); | |||||
@Getter(lazy=true) private static final String[] hostPrefixes = Arrays.stream(stream2string("bubble/host-prefixes.txt") | |||||
.split("\n")) | |||||
.filter(ZillaRuntime::notEmpty) | |||||
.toArray(String[]::new); | |||||
public static String newNodeHostname() { | public static String newNodeHostname() { | ||||
final String rand0 = getHostPrefixes()[RandomUtils.nextInt(0, getHostPrefixes().length)]; | final String rand0 = getHostPrefixes()[RandomUtils.nextInt(0, getHostPrefixes().length)]; | ||||
@@ -243,6 +247,14 @@ public class ApiConstants { | |||||
return rand0+"-"+(rand1 < 10 ? "0"+rand1 : rand1)+rand2+"-"+rand3+rand4; | return rand0+"-"+(rand1 < 10 ? "0"+rand1 : rand1)+rand2+"-"+rand3+rand4; | ||||
} | } | ||||
public static String newNetworkName() { | |||||
final String rand0 = getHostPrefixes()[RandomUtils.nextInt(0, getHostPrefixes().length)]; | |||||
final String rand1 = randomAlphanumeric(2).toLowerCase() + RandomUtils.nextInt(10, 100) + randomAlphanumeric(1).toLowerCase(); | |||||
final String rand2 = randomAlphanumeric(2).toLowerCase() + RandomUtils.nextInt(10, 100) + randomAlphanumeric(1).toLowerCase(); | |||||
final String rand3 = randomAlphanumeric(2).toLowerCase() + RandomUtils.nextInt(10, 100) + randomAlphanumeric(1).toLowerCase(); | |||||
return rand0+"-"+rand1+"-"+rand2+"-"+rand3; | |||||
} | |||||
public static String getRemoteHost(Request req) { | public static String getRemoteHost(Request req) { | ||||
final String xff = req.getHeader("X-Forwarded-For"); | final String xff = req.getHeader("X-Forwarded-For"); | ||||
final String remoteHost = xff == null ? req.getRemoteAddr() : xff; | final String remoteHost = xff == null ? req.getRemoteAddr() : xff; | ||||
@@ -162,7 +162,7 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||||
} | } | ||||
if (account.hasParent()) { | if (account.hasParent()) { | ||||
final AccountInitializer init = new AccountInitializer(account, this, messageDAO, selfNodeService); | |||||
final AccountInitializer init = new AccountInitializer(account, this, policyDAO, messageDAO, selfNodeService); | |||||
account.setAccountInitializer(init); | account.setAccountInitializer(init); | ||||
daemon(init); | daemon(init); | ||||
} | } | ||||
@@ -6,6 +6,7 @@ package bubble.dao.account; | |||||
import bubble.dao.account.message.AccountMessageDAO; | import bubble.dao.account.message.AccountMessageDAO; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.account.AccountPolicy; | |||||
import bubble.model.account.message.AccountAction; | import bubble.model.account.message.AccountAction; | ||||
import bubble.model.account.message.AccountMessage; | import bubble.model.account.message.AccountMessage; | ||||
import bubble.model.account.message.AccountMessageType; | import bubble.model.account.message.AccountMessageType; | ||||
@@ -31,6 +32,7 @@ public class AccountInitializer implements Runnable { | |||||
private Account account; | private Account account; | ||||
private AccountDAO accountDAO; | private AccountDAO accountDAO; | ||||
private AccountPolicyDAO policyDAO; | |||||
private AccountMessageDAO messageDAO; | private AccountMessageDAO messageDAO; | ||||
private SelfNodeService selfNodeService; | private SelfNodeService selfNodeService; | ||||
@@ -50,9 +52,14 @@ public class AccountInitializer implements Runnable { | |||||
public Exception getError() { return error.get(); } | public Exception getError() { return error.get(); } | ||||
public boolean hasError () { return getError() != null; } | public boolean hasError () { return getError() != null; } | ||||
public AccountInitializer(Account account, AccountDAO accountDAO, AccountMessageDAO messageDAO, SelfNodeService selfNodeService) { | |||||
public AccountInitializer(Account account, | |||||
AccountDAO accountDAO, | |||||
AccountPolicyDAO policyDAO, | |||||
AccountMessageDAO messageDAO, | |||||
SelfNodeService selfNodeService) { | |||||
this.account = account; | this.account = account; | ||||
this.accountDAO = accountDAO; | this.accountDAO = accountDAO; | ||||
this.policyDAO = policyDAO; | |||||
this.messageDAO = messageDAO; | this.messageDAO = messageDAO; | ||||
this.selfNodeService = selfNodeService; | this.selfNodeService = selfNodeService; | ||||
} | } | ||||
@@ -73,10 +80,6 @@ public class AccountInitializer implements Runnable { | |||||
log.warn("aborting!"); | log.warn("aborting!"); | ||||
return; | return; | ||||
} | } | ||||
if (account.hasPolicy() && account.getPolicy().hasAccountContacts()) { | |||||
messageDAO.sendVerifyRequest(account.getRemoteHost(), account, account.getPolicy().getAccountContacts()[0]); | |||||
} | |||||
success = true; | success = true; | ||||
break; | break; | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
@@ -87,18 +90,22 @@ public class AccountInitializer implements Runnable { | |||||
if (!success) throw lastEx; | if (!success) throw lastEx; | ||||
if (account.sendWelcomeEmail()) { | if (account.sendWelcomeEmail()) { | ||||
final BubbleNetwork thisNetwork = selfNodeService.getThisNetwork(); | final BubbleNetwork thisNetwork = selfNodeService.getThisNetwork(); | ||||
final String accountUuid = account.getUuid(); | |||||
final AccountPolicy policy = policyDAO.findSingleByAccount(accountUuid); | |||||
final String contact = policy != null && policy.hasAccountContacts() ? policy.getAccountContacts()[0].getUuid() : null; | |||||
if (contact == null) die("no contact found for welcome message: account="+accountUuid); | |||||
messageDAO.create(new AccountMessage() | messageDAO.create(new AccountMessage() | ||||
.setRemoteHost(account.getRemoteHost()) | .setRemoteHost(account.getRemoteHost()) | ||||
.setAccount(account.getUuid()) | |||||
.setName(account.getUuid()) | |||||
.setAccount(accountUuid) | |||||
.setName(accountUuid) | |||||
.setNetwork(thisNetwork.getUuid()) | .setNetwork(thisNetwork.getUuid()) | ||||
.setMessageType(AccountMessageType.notice) | .setMessageType(AccountMessageType.notice) | ||||
.setAction(AccountAction.welcome) | .setAction(AccountAction.welcome) | ||||
.setTarget(ActionTarget.account)); | |||||
.setTarget(ActionTarget.account) | |||||
.setContact(contact)); | |||||
} | } | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
error.set(e); | error.set(e); | ||||
// todo: send to errbit | |||||
die("error: "+e, e); | die("error: "+e, e); | ||||
} finally { | } finally { | ||||
completed.set(true); | completed.set(true); | ||||
@@ -166,6 +166,9 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO<AccountMessage> { | |||||
} | } | ||||
public AccountMessage findOperationRequest(AccountMessage basis) { | public AccountMessage findOperationRequest(AccountMessage basis) { | ||||
if (basis.getAction() == AccountAction.welcome && basis.getTarget() == ActionTarget.account) { | |||||
return findWelcomeNotice(basis); | |||||
} | |||||
return findByUniqueFields("account", basis.getAccount(), | return findByUniqueFields("account", basis.getAccount(), | ||||
"name", basis.getName(), | "name", basis.getName(), | ||||
"requestId", basis.getRequestId(), | "requestId", basis.getRequestId(), | ||||
@@ -174,6 +177,15 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO<AccountMessage> { | |||||
"target", basis.getTarget()); | "target", basis.getTarget()); | ||||
} | } | ||||
public AccountMessage findWelcomeNotice(AccountMessage basis) { | |||||
return findByUniqueFields("account", basis.getAccount(), | |||||
"name", basis.getName(), | |||||
"requestId", basis.getRequestId(), | |||||
"messageType", AccountMessageType.notice, | |||||
"action", AccountAction.welcome, | |||||
"target", ActionTarget.account); | |||||
} | |||||
public List<AccountMessage> findOperationDenials(AccountMessage basis) { | public List<AccountMessage> findOperationDenials(AccountMessage basis) { | ||||
if (basis == null) { | if (basis == null) { | ||||
return Collections.emptyList(); | return Collections.emptyList(); | ||||
@@ -45,6 +45,7 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> { | |||||
if (errors.isInvalid()) throw invalidEx(errors); | if (errors.isInvalid()) throw invalidEx(errors); | ||||
if (errors.hasSuggestedName()) network.setName(errors.getSuggestedName()); | if (errors.hasSuggestedName()) network.setName(errors.getSuggestedName()); | ||||
} | } | ||||
if (!network.hasNickname()) network.setNickname(network.getName()); | |||||
final AnsibleInstallType installType = network.hasForkHost() && configuration.isSageLauncher() | final AnsibleInstallType installType = network.hasForkHost() && configuration.isSageLauncher() | ||||
? AnsibleInstallType.sage | ? AnsibleInstallType.sage | ||||
: AnsibleInstallType.node; | : AnsibleInstallType.node; | ||||
@@ -1,3 +1,7 @@ | |||||
/** | |||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | |||||
*/ | |||||
package bubble.model.account; | package bubble.model.account; | ||||
import org.cobbzilla.wizard.auth.LoginRequest; | import org.cobbzilla.wizard.auth.LoginRequest; | ||||
@@ -97,13 +97,17 @@ public class AccountMessage extends IdentifiableBase implements HasAccount { | |||||
public String templateName(String basename) { return getMessageType()+"/"+ getAction()+"/"+getTarget()+"/"+basename+".hbs"; } | public String templateName(String basename) { return getMessageType()+"/"+ getAction()+"/"+getTarget()+"/"+basename+".hbs"; } | ||||
public long tokenTimeoutSeconds(AccountPolicy policy) { | public long tokenTimeoutSeconds(AccountPolicy policy) { | ||||
if (getMessageType() != AccountMessageType.request) return -1; | |||||
switch (getTarget()) { | |||||
case account: return policy.getAccountOperationTimeout()/1000; | |||||
case network: return policy.getNodeOperationTimeout()/1000; | |||||
default: | |||||
log.warn("tokenTimeout: invalid target: "+getTarget()); | |||||
return -1; | |||||
// only requests and welcome message get tokens (welcome messages also verify the initial email address) | |||||
if (getMessageType() == AccountMessageType.request || (getMessageType() == AccountMessageType.notice && getAction() == AccountAction.welcome)) { | |||||
switch (getTarget()) { | |||||
case account: return policy.getAccountOperationTimeout()/1000; | |||||
case network: return policy.getNodeOperationTimeout()/1000; | |||||
default: | |||||
log.warn("tokenTimeout: invalid target: "+getTarget()); | |||||
return -1; | |||||
} | |||||
} else { | |||||
return -1; | |||||
} | } | ||||
} | } | ||||
@@ -132,6 +132,10 @@ public class AccountPlan extends IdentifiableBase implements HasNetwork { | |||||
@Getter @Setter private String refundError; | @Getter @Setter private String refundError; | ||||
// Fields below are used when creating a new plan, to also create the network associated with it | // Fields below are used when creating a new plan, to also create the network associated with it | ||||
@Size(max=NAME_MAXLEN, message="err.nick.tooLong") | |||||
@Transient @Getter @Setter private transient String nickname; | |||||
public boolean hasNickname () { return !empty(nickname); } | |||||
@Size(max=10000, message="err.description.length") | @Size(max=10000, message="err.description.length") | ||||
@Transient @Getter @Setter private transient String description; | @Transient @Getter @Setter private transient String description; | ||||
@@ -171,6 +175,7 @@ public class AccountPlan extends IdentifiableBase implements HasNetwork { | |||||
CloudService storage) { | CloudService storage) { | ||||
return new BubbleNetwork() | return new BubbleNetwork() | ||||
.setName(getName()) | .setName(getName()) | ||||
.setNickname(getNickname()) | |||||
.setDescription(getDescription()) | .setDescription(getDescription()) | ||||
.setLocale(getLocale()) | .setLocale(getLocale()) | ||||
.setTimezone(getTimezone()) | .setTimezone(getTimezone()) | ||||
@@ -61,7 +61,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||||
public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBubbleTags<BubbleNetwork> { | public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBubbleTags<BubbleNetwork> { | ||||
public static final String[] UPDATE_FIELDS = { | public static final String[] UPDATE_FIELDS = { | ||||
"footprint", "description", "locale", "timezone", "state", "syncPassword", "launchLock" | |||||
"nickname", "footprint", "description", "locale", "timezone", "state", "syncPassword", "launchLock" | |||||
}; | }; | ||||
public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, | public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, | ||||
"name", "domain", "sendErrors", "sendMetrics"); | "name", "domain", "sendErrors", "sendMetrics"); | ||||
@@ -110,7 +110,13 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||||
@Transient @JsonIgnore public String getNetworkDomain () { return name + "." + domainName; } | @Transient @JsonIgnore public String getNetworkDomain () { return name + "." + domainName; } | ||||
@Column(nullable=false) @ECField(index=50) | |||||
@ECSearchable(filter=true) @ECField(index=50) | |||||
@ECIndex @Column(nullable=false, length=NAME_MAXLEN) | |||||
@Size(min=1, max=NAME_MAXLEN, message="err.nick.tooLong") | |||||
@Getter @Setter private String nickname; | |||||
public boolean hasNickname () { return !empty(nickname); } | |||||
@Column(nullable=false) @ECField(index=60) | |||||
@Getter @Setter private Integer sslPort; | @Getter @Setter private Integer sslPort; | ||||
@Transient @JsonIgnore public String getPublicUri() { | @Transient @JsonIgnore public String getPublicUri() { | ||||
@@ -121,37 +127,37 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||||
return getUuid().equals(ROOT_NETWORK_UUID) ? configuration.getPublicUriBase() : getPublicUri(); | return getUuid().equals(ROOT_NETWORK_UUID) ? configuration.getPublicUriBase() : getPublicUri(); | ||||
} | } | ||||
@ECIndex @Column(nullable=false, updatable=false, length=60) | |||||
@ECIndex @Column(nullable=false, updatable=false, length=60) @ECField(index=70) | |||||
@Enumerated(EnumType.STRING) | @Enumerated(EnumType.STRING) | ||||
@Getter @Setter private AnsibleInstallType installType; | @Getter @Setter private AnsibleInstallType installType; | ||||
@ECSearchable @ECField(index=70) | |||||
@ECSearchable @ECField(index=80) | |||||
@ECForeignKey(entity=AccountSshKey.class) | @ECForeignKey(entity=AccountSshKey.class) | ||||
@Column(length=UUID_MAXLEN) | @Column(length=UUID_MAXLEN) | ||||
@Getter @Setter private String sshKey; | @Getter @Setter private String sshKey; | ||||
public boolean hasSshKey () { return !empty(sshKey); } | public boolean hasSshKey () { return !empty(sshKey); } | ||||
@ECSearchable @ECField(index=80) | |||||
@ECSearchable @ECField(index=90) | |||||
@ECIndex @Column(nullable=false, updatable=false, length=20) | @ECIndex @Column(nullable=false, updatable=false, length=20) | ||||
@Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; | @Enumerated(EnumType.STRING) @Getter @Setter private ComputeNodeSizeType computeSizeType; | ||||
@ECSearchable @ECField(index=90) | |||||
@ECSearchable @ECField(index=100) | |||||
@ECForeignKey(entity=BubbleFootprint.class) | @ECForeignKey(entity=BubbleFootprint.class) | ||||
@Column(nullable=false, length=UUID_MAXLEN) | @Column(nullable=false, length=UUID_MAXLEN) | ||||
@Getter @Setter private String footprint; | @Getter @Setter private String footprint; | ||||
public boolean hasFootprint () { return footprint != null; } | public boolean hasFootprint () { return footprint != null; } | ||||
@ECSearchable @ECField(index=100) | |||||
@ECSearchable @ECField(index=110) | |||||
@ECForeignKey(entity=CloudService.class) | @ECForeignKey(entity=CloudService.class) | ||||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | @Column(nullable=false, updatable=false, length=UUID_MAXLEN) | ||||
@Getter @Setter private String storage; | @Getter @Setter private String storage; | ||||
@ECSearchable(filter=true) @ECField(index=110) | |||||
@ECSearchable(filter=true) @ECField(index=120) | |||||
@Size(max=10000, message="err.description.length") | @Size(max=10000, message="err.description.length") | ||||
@Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(10000+ENC_PAD)+")") | @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(10000+ENC_PAD)+")") | ||||
@Getter @Setter private String description; | @Getter @Setter private String description; | ||||
@ECSearchable @ECField(index=120) | |||||
@ECSearchable @ECField(index=130) | |||||
@Size(max=20, message="err.locale.length") | @Size(max=20, message="err.locale.length") | ||||
@Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(20+ENC_PAD)+") NOT NULL") | @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(20+ENC_PAD)+") NOT NULL") | ||||
@Getter @Setter private String locale = getDEFAULT_LOCALE(); | @Getter @Setter private String locale = getDEFAULT_LOCALE(); | ||||
@@ -159,27 +165,27 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||||
// A unicode timezone alias from: cobbzilla-utils/src/main/resources/org/cobbzilla/util/time/unicode-timezones.xml | // A unicode timezone alias from: cobbzilla-utils/src/main/resources/org/cobbzilla/util/time/unicode-timezones.xml | ||||
// All unicode aliases are guaranteed to map to a Linux timezone and a Java timezone | // All unicode aliases are guaranteed to map to a Linux timezone and a Java timezone | ||||
@ECSearchable @ECField(index=130) | |||||
@ECSearchable @ECField(index=140) | |||||
@Size(max=100, message="err.timezone.length") | @Size(max=100, message="err.timezone.length") | ||||
@Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100+ENC_PAD)+") NOT NULL") | @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100+ENC_PAD)+") NOT NULL") | ||||
@Getter @Setter private String timezone = "America/New_York"; | @Getter @Setter private String timezone = "America/New_York"; | ||||
@ECSearchable @ECField(index=140) | |||||
@ECSearchable @ECField(index=150) | |||||
@Column(nullable=false) | @Column(nullable=false) | ||||
@ECIndex @Getter @Setter private Boolean syncPassword; | @ECIndex @Getter @Setter private Boolean syncPassword; | ||||
public boolean syncPassword() { return bool(syncPassword); } | public boolean syncPassword() { return bool(syncPassword); } | ||||
@ECSearchable @ECField(index=150) | |||||
@ECSearchable @ECField(index=160) | |||||
@Column(nullable=false) | @Column(nullable=false) | ||||
@ECIndex @Getter @Setter private Boolean launchLock; | @ECIndex @Getter @Setter private Boolean launchLock; | ||||
public boolean launchLock() { return bool(launchLock); } | public boolean launchLock() { return bool(launchLock); } | ||||
@ECSearchable @ECField(index=160) | |||||
@ECSearchable @ECField(index=170) | |||||
@Column(nullable=false) | @Column(nullable=false) | ||||
@ECIndex @Getter @Setter private Boolean sendErrors; | @ECIndex @Getter @Setter private Boolean sendErrors; | ||||
public boolean sendErrors() { return bool(sendErrors); } | public boolean sendErrors() { return bool(sendErrors); } | ||||
@ECSearchable @ECField(index=170) | |||||
@ECSearchable @ECField(index=180) | |||||
@Column(nullable=false) | @Column(nullable=false) | ||||
@ECIndex @Getter @Setter private Boolean sendMetrics; | @ECIndex @Getter @Setter private Boolean sendMetrics; | ||||
public boolean sendMetrics() { return bool(sendMetrics); } | public boolean sendMetrics() { return bool(sendMetrics); } | ||||
@@ -190,7 +196,7 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||||
public boolean hasForkHost () { return !empty(forkHost); } | public boolean hasForkHost () { return !empty(forkHost); } | ||||
public boolean fork() { return hasForkHost(); } | public boolean fork() { return hasForkHost(); } | ||||
@ECSearchable @ECField(index=160) | |||||
@ECSearchable @ECField(index=190) | |||||
@Column(length=20) | @Column(length=20) | ||||
@Enumerated(EnumType.STRING) @Getter @Setter private BubbleNetworkState state = created; | @Enumerated(EnumType.STRING) @Getter @Setter private BubbleNetworkState state = created; | ||||
@@ -214,7 +214,7 @@ public class AuthResource { | |||||
final ValidationResult errors = request.validateEmail(); | final ValidationResult errors = request.validateEmail(); | ||||
if (errors.isValid()) { | if (errors.isValid()) { | ||||
final Account existing = accountDAO.findByEmail(request.getEmail()); | final Account existing = accountDAO.findByEmail(request.getEmail()); | ||||
if (existing != null) errors.addViolation("err.name.registered", "Name is already registered: ", request.getEmail()); | |||||
if (existing != null) errors.addViolation("err.email.registered", "Email is already registered: ", request.getEmail()); | |||||
} | } | ||||
final ConstraintViolationBean passwordViolation = validatePassword(request.getPassword()); | final ConstraintViolationBean passwordViolation = validatePassword(request.getPassword()); | ||||
@@ -45,6 +45,7 @@ import static bubble.model.cloud.BubbleNetwork.validateHostname; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.string.ValidationRegexes.HOST_PATTERN; | import static org.cobbzilla.util.string.ValidationRegexes.HOST_PATTERN; | ||||
import static org.cobbzilla.util.string.ValidationRegexes.validateRegexMatches; | import static org.cobbzilla.util.string.ValidationRegexes.validateRegexMatches; | ||||
import static org.cobbzilla.wizard.model.NamedEntity.NAME_MAXLEN; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | import static org.cobbzilla.wizard.resources.ResourceUtil.*; | ||||
@Slf4j | @Slf4j | ||||
@@ -144,7 +145,18 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
validateName(request, errors); | |||||
if (!request.hasNickname()) { | |||||
if (request.hasName()) { | |||||
request.setNickname(request.getName()); | |||||
} else { | |||||
errors.addViolation("err.name.required"); | |||||
} | |||||
} | |||||
if (request.hasNickname() && request.getNickname().length() > NAME_MAXLEN) { | |||||
errors.addViolation("err.name.tooLong"); | |||||
} | |||||
// assign a random name for the network | |||||
request.setName(newNetworkName()); | |||||
} | } | ||||
log.info("setReferences: after calling validateName, request.name="+request.getName()); | log.info("setReferences: after calling validateName, request.name="+request.getName()); | ||||
@@ -38,10 +38,7 @@ import org.cobbzilla.wizard.cache.redis.HasRedisConfiguration; | |||||
import org.cobbzilla.wizard.cache.redis.RedisConfiguration; | import org.cobbzilla.wizard.cache.redis.RedisConfiguration; | ||||
import org.cobbzilla.wizard.client.ApiClientBase; | import org.cobbzilla.wizard.client.ApiClientBase; | ||||
import org.cobbzilla.wizard.server.RestServerHarness; | import org.cobbzilla.wizard.server.RestServerHarness; | ||||
import org.cobbzilla.wizard.server.config.HasDatabaseConfiguration; | |||||
import org.cobbzilla.wizard.server.config.LegalInfo; | |||||
import org.cobbzilla.wizard.server.config.PgRestServerConfiguration; | |||||
import org.cobbzilla.wizard.server.config.RecaptchaConfig; | |||||
import org.cobbzilla.wizard.server.config.*; | |||||
import org.cobbzilla.wizard.util.ClasspathScanner; | import org.cobbzilla.wizard.util.ClasspathScanner; | ||||
import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||
import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||
@@ -87,6 +84,7 @@ public class BubbleConfiguration extends PgRestServerConfiguration | |||||
public static final String TAG_SSL_PORT = "sslPort"; | public static final String TAG_SSL_PORT = "sslPort"; | ||||
public static final String TAG_PROMO_CODE_POLICY = "promoCodePolicy"; | public static final String TAG_PROMO_CODE_POLICY = "promoCodePolicy"; | ||||
public static final String TAG_REQUIRE_SEND_METRICS = "requireSendMetrics"; | public static final String TAG_REQUIRE_SEND_METRICS = "requireSendMetrics"; | ||||
public static final String TAG_SUPPORT = "support"; | |||||
public static final String TAG_RESTORE_MODE = "isInRestoringStatus"; | public static final String TAG_RESTORE_MODE = "isInRestoringStatus"; | ||||
public static final String DEFAULT_LOCAL_STORAGE_DIR = HOME_DIR + "/.bubble_local_storage"; | public static final String DEFAULT_LOCAL_STORAGE_DIR = HOME_DIR + "/.bubble_local_storage"; | ||||
@@ -296,7 +294,8 @@ public class BubbleConfiguration extends PgRestServerConfiguration | |||||
{TAG_LOCKED, accountDAO.locked()}, | {TAG_LOCKED, accountDAO.locked()}, | ||||
{TAG_LAUNCH_LOCK, isSageLauncher() || thisNetwork == null ? null : thisNetwork.launchLock()}, | {TAG_LAUNCH_LOCK, isSageLauncher() || thisNetwork == null ? null : thisNetwork.launchLock()}, | ||||
{TAG_RESTORE_MODE, thisNode.wasRestored()}, | {TAG_RESTORE_MODE, thisNode.wasRestored()}, | ||||
{TAG_SSL_PORT, getDefaultSslPort()} | |||||
{TAG_SSL_PORT, getDefaultSslPort()}, | |||||
{TAG_SUPPORT, getSupport()} | |||||
})); | })); | ||||
} | } | ||||
return publicSystemConfigs.get(); | return publicSystemConfigs.get(); | ||||
@@ -52,7 +52,8 @@ public class StandardAccountMessageService implements AccountMessageService { | |||||
final Account account = accountDAO.findByUuid(accountUuid); | final Account account = accountDAO.findByUuid(accountUuid); | ||||
AccountPolicy policy = policyDAO.findSingleByAccount(accountUuid); | AccountPolicy policy = policyDAO.findSingleByAccount(accountUuid); | ||||
if (policy == null) { | if (policy == null) { | ||||
policy = policyDAO.create(new AccountPolicy().setAccount(accountUuid)); | |||||
log.warn("send("+message+"): no policy for account"); | |||||
return false; | |||||
} | } | ||||
final List<AccountContact> contacts = policy.getAllowedContacts(message); | final List<AccountContact> contacts = policy.getAllowedContacts(message); | ||||
if (contacts.isEmpty()) { | if (contacts.isEmpty()) { | ||||
@@ -136,9 +137,11 @@ public class StandardAccountMessageService implements AccountMessageService { | |||||
if (account == null) account = accountDAO.findByUuid(approval.getAccount()); | if (account == null) account = accountDAO.findByUuid(approval.getAccount()); | ||||
final AccountMessageApprovalStatus approvalStatus = messageDAO.requestApproved(account, approval, token, data); | final AccountMessageApprovalStatus approvalStatus = messageDAO.requestApproved(account, approval, token, data); | ||||
if (approvalStatus == AccountMessageApprovalStatus.ok_confirmed) { | if (approvalStatus == AccountMessageApprovalStatus.ok_confirmed) { | ||||
final AccountMessage request = messageDAO.findOperationRequest(approval); | |||||
if (request == null) throw invalidEx("err.approvalToken.invalid", "Request could not be found for approval: "+approval); | |||||
final AccountPolicy policy = policyDAO.findSingleByAccount(account.getUuid()); | final AccountPolicy policy = policyDAO.findSingleByAccount(account.getUuid()); | ||||
final AccountMessage confirm = messageDAO.create(new AccountMessage(approval).setMessageType(AccountMessageType.confirmation)); | final AccountMessage confirm = messageDAO.create(new AccountMessage(approval).setMessageType(AccountMessageType.confirmation)); | ||||
approval.setRequest(messageDAO.findOperationRequest(approval)); | |||||
approval.setRequest(request); | |||||
approval.setRequestContact(policy.findContactByUuid(approval.getRequest().getContact())); | approval.setRequestContact(policy.findContactByUuid(approval.getRequest().getContact())); | ||||
getCompletionHandler(approval).confirm(approval, data); | getCompletionHandler(approval).confirm(approval, data); | ||||
@@ -158,6 +161,7 @@ public class StandardAccountMessageService implements AccountMessageService { | |||||
throw invalidEx("err.approvalToken.invalid", "Approval cannot proceed: "+approvalStatus, approvalStatus.name()); | throw invalidEx("err.approvalToken.invalid", "Approval cannot proceed: "+approvalStatus, approvalStatus.name()); | ||||
} | } | ||||
@Getter(lazy=true) private final AccountMessageCompletionHandler accountWelcomeHandler = configuration.autowire(new AccountVerifyHandler()); | |||||
@Getter(lazy=true) private final AccountMessageCompletionHandler accountLoginHandler = configuration.autowire(new AccountLoginHandler()); | @Getter(lazy=true) private final AccountMessageCompletionHandler accountLoginHandler = configuration.autowire(new AccountLoginHandler()); | ||||
@Getter(lazy=true) private final AccountMessageCompletionHandler accountPasswordHandler = configuration.autowire(new AccountPasswordHandler()); | @Getter(lazy=true) private final AccountMessageCompletionHandler accountPasswordHandler = configuration.autowire(new AccountPasswordHandler()); | ||||
@Getter(lazy=true) private final AccountMessageCompletionHandler accountVerifyHandler = configuration.autowire(new AccountVerifyHandler()); | @Getter(lazy=true) private final AccountMessageCompletionHandler accountVerifyHandler = configuration.autowire(new AccountVerifyHandler()); | ||||
@@ -170,6 +174,7 @@ public class StandardAccountMessageService implements AccountMessageService { | |||||
@Getter(lazy=true) private final Map<String, AccountMessageCompletionHandler> confirmationHandlers = initConfirmationHandlers(); | @Getter(lazy=true) private final Map<String, AccountMessageCompletionHandler> confirmationHandlers = initConfirmationHandlers(); | ||||
private HashMap<String, AccountMessageCompletionHandler> initConfirmationHandlers() { | private HashMap<String, AccountMessageCompletionHandler> initConfirmationHandlers() { | ||||
final HashMap<String, AccountMessageCompletionHandler> handlers = new HashMap<>(); | final HashMap<String, AccountMessageCompletionHandler> handlers = new HashMap<>(); | ||||
handlers.put(ActionTarget.account+":"+AccountAction.welcome, getAccountWelcomeHandler()); | |||||
handlers.put(ActionTarget.account+":"+AccountAction.login, getAccountLoginHandler()); | handlers.put(ActionTarget.account+":"+AccountAction.login, getAccountLoginHandler()); | ||||
handlers.put(ActionTarget.account+":"+AccountAction.password, getAccountPasswordHandler()); | handlers.put(ActionTarget.account+":"+AccountAction.password, getAccountPasswordHandler()); | ||||
handlers.put(ActionTarget.account+":"+AccountAction.verify, getAccountVerifyHandler()); | handlers.put(ActionTarget.account+":"+AccountAction.verify, getAccountVerifyHandler()); | ||||
@@ -1 +1 @@ | |||||
bubble.version=0.11.1 | |||||
bubble.version=0.11.2 |
@@ -71,6 +71,10 @@ errorApi: | |||||
key: {{ERRBIT_KEY}} | key: {{ERRBIT_KEY}} | ||||
env: {{ERRBIT_ENV}} | env: {{ERRBIT_ENV}} | ||||
support: | |||||
email: '{{SUPPORT_EMAIL}}' | |||||
site: '{{SUPPORT_SITE}}' | |||||
localNotificationStrategy: {{#exists BUBBLE_LOCAL_NOTIFY}}{{BUBBLE_LOCAL_NOTIFY}}{{else}}inline{{/exists}} | localNotificationStrategy: {{#exists BUBBLE_LOCAL_NOTIFY}}{{BUBBLE_LOCAL_NOTIFY}}{{else}}inline{{/exists}} | ||||
letsencryptEmail: {{LETSENCRYPT_EMAIL}} | letsencryptEmail: {{LETSENCRYPT_EMAIL}} | ||||
@@ -2,8 +2,10 @@ Hello {{account.name}}, | |||||
Welcome to Bubble! | Welcome to Bubble! | ||||
You can login with your username and password here: {{publicUri}}/login | |||||
Please confirm your email address using this link: | |||||
If you have any questions, please contact your Bubble Administrator. | |||||
{{publicUri}}/me/action?approve={{confirmationToken}} | |||||
{{#if configuration.hasSupportInfo}}If you have any questions or need help, please {{#if configuration.support.hasEmailAndSite}}contact {{configuration.support.email}} or visit {{configuration.support.site}}{{else}}{{#if configuration.support.hasEmail}}contact {{configuration.support.email}}{{else}}visit {{configuration.support.site}}{{/if}}{{/if}}{{/if}} | |||||
Happy bubbling! | Happy bubbling! |
@@ -3,24 +3,10 @@ Hello {{account.name}}, | |||||
Contact information has been added to your account named '{{account.name}}' on {{network.networkDomain}} | Contact information has been added to your account named '{{account.name}}' on {{network.networkDomain}} | ||||
{{#string_compare contact.uuid '==' message.contact}}{{contact.type}} - {{contact.info}}{{else}}{{message.requestContact.type}}{{#if message.requestContact.isSms}}{{message.requestContact.info}}{{/if}}{{/string_compare}} {{#if message.requestContact.nick}}({{message.requestContact.nick}}){{/if}} | {{#string_compare contact.uuid '==' message.contact}}{{contact.type}} - {{contact.info}}{{else}}{{message.requestContact.type}}{{#if message.requestContact.isSms}}{{message.requestContact.info}}{{/if}}{{/string_compare}} {{#if message.requestContact.nick}}({{message.requestContact.nick}}){{/if}} | ||||
If you did not make this request or would like to cancel this request, please click this link: | |||||
{{publicUri}}/me/action?deny={{confirmationToken}} | |||||
{{#string_compare contact.uuid '==' message.contact}} | {{#string_compare contact.uuid '==' message.contact}} | ||||
If you DID make this request and are ready to verify this contact information, click the link below, | |||||
or enter the value {{confirmationToken}} when the verification code is requested. | |||||
To confirm this contact information, follow this link: | |||||
{{publicUri}}/me/action?approve={{confirmationToken}} | {{publicUri}}/me/action?approve={{confirmationToken}} | ||||
{{/string_compare}} | {{/string_compare}} | ||||
Thank you for using Bubble! | Thank you for using Bubble! |
@@ -184,8 +184,8 @@ label_field_nodes_ip6=IPv6 | |||||
# New Network page | # New Network page | ||||
message_no_contacts=No authorized contact info found | message_no_contacts=No authorized contact info found | ||||
link_label_no_contacts=Add an email address or SMS-enabled phone number | link_label_no_contacts=Add an email address or SMS-enabled phone number | ||||
message_no_verified_contacts=No verified contact info found | |||||
message_no_verified_contacts_subtext=Before creating your first Bubble, please verify the contact information shown below | |||||
message_no_verified_contacts=Please verify your email address | |||||
message_no_verified_contacts_subtext=Check your email and follow the link to verify your email address | |||||
form_title_new_network=New Bubble | form_title_new_network=New Bubble | ||||
field_label_network_name=Name | field_label_network_name=Name | ||||
@@ -742,7 +742,7 @@ err.tgzB64.invalid.missingTasksMainYml=No tasks/main.yml file found for role in | |||||
err.tgzB64.invalid.writingToStorage=Error writing tgz to storage | err.tgzB64.invalid.writingToStorage=Error writing tgz to storage | ||||
err.tgzB64.invalid.readingFromStorage=Error reading tgz from storage | err.tgzB64.invalid.readingFromStorage=Error reading tgz from storage | ||||
err.tgzB64.required=tgzB64 is required | err.tgzB64.required=tgzB64 is required | ||||
err.totpKey.length=TOTP key is required | |||||
err.totpKey.length=TOTP key is too long | |||||
err.type.notVerifiable=Type is not verifiable | err.type.notVerifiable=Type is not verifiable | ||||
err.type.invalid=Type is invalid | err.type.invalid=Type is invalid | ||||
err.type.required=Type is required | err.type.required=Type is required | ||||
@@ -12,6 +12,13 @@ message_undefined=undefined | |||||
# Display of percent values has localized variations | # Display of percent values has localized variations | ||||
label_percent={{percent}}% | label_percent={{percent}}% | ||||
# Support | |||||
title_support=Bubble Support | |||||
support_preamble=To get help with Bubble: | |||||
support_site_link=Visit our Support Website | |||||
support_email_link=Send us an email | |||||
support_not_available=Sorry, no support options are available | |||||
# Legal page links | # Legal page links | ||||
title_legal_topics=Legal Stuff | title_legal_topics=Legal Stuff | ||||
legal_topics=terms,privacy,source,license,3rdParty_licenses | legal_topics=terms,privacy,source,license,3rdParty_licenses | ||||
@@ -264,6 +271,7 @@ form_title_login=Login | |||||
form_title_restore=Restore | form_title_restore=Restore | ||||
field_label_username=Username | field_label_username=Username | ||||
field_label_password=Password | field_label_password=Password | ||||
field_label_password_guidance=Password must be at least 8 characters long.<br/>Password must contain at least one letter, one number, and one special character. | |||||
field_label_confirm_password=Confirm Password | field_label_confirm_password=Confirm Password | ||||
field_label_unlock_key=Unlock Key | field_label_unlock_key=Unlock Key | ||||
field_label_restore_short_key=Short Key (6 letters) | field_label_restore_short_key=Short Key (6 letters) | ||||
@@ -1,2 +1,2 @@ | |||||
{{network.networkDomain}}: Welcome to Bubble, {{account.name}}! | {{network.networkDomain}}: Welcome to Bubble, {{account.name}}! | ||||
Login here: {{publicUri}}/login | |||||
Verify your email: {{publicUri}}/me/action?approve={{confirmationToken}} |
@@ -1 +1,2 @@ | |||||
{{network.networkDomain}}: {{#string_compare contact.uuid '==' message.contact}}SMS Phone added: {{contact.info}} - To approve, use code {{confirmationToken}} or use: {{publicUri}}/me/action?approve={{confirmationToken}} - To deny: {{publicUri}}/me/action?deny={{confirmationToken}}{{else}}{{#if message.requestContact.isEmail}}Email added: {{message.requestContact.info}}{{else}}Auth added: {{message.requestContact.type}}{{/if}} - To deny: {{publicUri}}/me/action?deny={{confirmationToken}}{{/string_compare}} | |||||
{{network.networkDomain}}: {{#string_compare contact.uuid '==' message.contact}}SMS Phone added: {{contact.info}} | |||||
To approve: {{publicUri}}/me/action?approve={{confirmationToken}}{{else}}{{#if message.requestContact.isEmail}}Email added: {{message.requestContact.info}}{{else}}Auth added: {{message.requestContact.type}}{{/if}}{{/string_compare}} |
@@ -195,7 +195,7 @@ | |||||
}, | }, | ||||
{ | { | ||||
"comment": "new session, register new user, fails because username is already used", | |||||
"comment": "new session, register new user, fails because email is already used", | |||||
"request": { | "request": { | ||||
"session": "new", | "session": "new", | ||||
"uri": "auth/register", | "uri": "auth/register", | ||||
@@ -207,7 +207,7 @@ | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"status": 422, | "status": 422, | ||||
"check": [ {"condition": "json.has('err.name.registered')"} ] | |||||
"check": [ {"condition": "json.has('err.email.registered')"} ] | |||||
} | } | ||||
}, | }, | ||||
@@ -3,7 +3,55 @@ | |||||
"comment": "create new account and login", | "comment": "create new account and login", | ||||
"include": "new_account", | "include": "new_account", | ||||
"params": { | "params": { | ||||
"email": "user-multifactor_auth@example.com" | |||||
"email": "user-multifactor_auth_registered@example.com" | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "resend verification message for registration email", | |||||
"request": { | |||||
"uri": "users/{{userAccount.name}}/policy/contacts/verify", | |||||
"entity": { | |||||
"type": "email", | |||||
"info": "user-multifactor_auth_registered@example.com" | |||||
} | |||||
} | |||||
}, | |||||
{ | |||||
"before": "sleep 1s", | |||||
"comment": "as root, check inbox for email verification message for registration email", | |||||
"request": { | |||||
"session": "rootSession", | |||||
"uri": "debug/inbox/email/user-multifactor_auth_registered@example.com?action=verify" | |||||
}, | |||||
"response": { | |||||
"store": "userInbox", | |||||
"check": [ | |||||
{"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, | |||||
{"condition": "'{{json.[0].ctx.message.action}}' == 'verify'"}, | |||||
{"condition": "'{{json.[0].ctx.message.target}}' == 'account'"} | |||||
] | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "as user, approve email verification request for registration email", | |||||
"request": { | |||||
"session": "userSession", | |||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "add second email to account policy", | |||||
"request": { | |||||
"uri": "users/{{userAccount.name}}/policy/contacts", | |||||
"entity": { | |||||
"type": "email", | |||||
"info": "user-multifactor_auth@example.com" | |||||
} | |||||
} | } | ||||
}, | }, | ||||
@@ -14,9 +62,9 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 1"}, | |||||
{"condition": "!json.getAccountContacts()[0].authFactor()"}, | |||||
{"condition": "!json.getAccountContacts()[0].verified()"} | |||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "!json.getAccountContacts()[1].authFactor()"}, | |||||
{"condition": "!json.getAccountContacts()[1].verified()"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -72,7 +120,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
} | } | ||||
}, | }, | ||||
@@ -84,9 +132,9 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 1"}, | |||||
{"condition": "!json.getAccountContacts()[0].authFactor()"}, | |||||
{"condition": "json.getAccountContacts()[0].verified()"} | |||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "!json.getAccountContacts()[1].authFactor()"}, | |||||
{"condition": "json.getAccountContacts()[1].verified()"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -117,8 +165,8 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 1"}, | |||||
{"condition": "json.getAccountContacts()[0].requiredAuthFactor()"} | |||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "json.getAccountContacts()[1].requiredAuthFactor()"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -170,7 +218,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"sessionName": "userSession", | "sessionName": "userSession", | ||||
@@ -194,9 +242,9 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "json.getAccountContacts()[0].requiredAuthFactor()"}, | |||||
{"condition": "json.getAccountContacts()[1].requiredAuthFactor()"} | |||||
{"condition": "json.getAccountContacts().length == 3"}, | |||||
{"condition": "json.getAccountContacts()[1].requiredAuthFactor()"}, | |||||
{"condition": "json.getAccountContacts()[2].requiredAuthFactor()"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -248,7 +296,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"status": 422, | "status": 422, | ||||
@@ -262,7 +310,7 @@ | |||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [ | "entity": [ | ||||
{"name": "account", "value": "user-multifactor_auth@example.com"}, | |||||
{"name": "account", "value": "user-multifactor_auth_registered@example.com"}, | |||||
{"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} | {"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} | ||||
] | ] | ||||
}, | }, | ||||
@@ -308,8 +356,8 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 1"}, | |||||
{"condition": "json.getAccountContacts()[0].requiredAuthFactor()"} | |||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "json.getAccountContacts()[1].requiredAuthFactor()"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -372,7 +420,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
} | } | ||||
}, | }, | ||||
@@ -384,10 +432,10 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "json.getAccountContacts()[1].getType().name() == 'sms'"}, | |||||
{"condition": "!json.getAccountContacts()[1].authFactor()"}, | |||||
{"condition": "json.getAccountContacts()[1].verified()"} | |||||
{"condition": "json.getAccountContacts().length == 3"}, | |||||
{"condition": "json.getAccountContacts()[2].getType().name() == 'sms'"}, | |||||
{"condition": "!json.getAccountContacts()[2].authFactor()"}, | |||||
{"condition": "json.getAccountContacts()[2].verified()"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -459,7 +507,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"status": 422, | "status": 422, | ||||
@@ -473,7 +521,7 @@ | |||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [ | "entity": [ | ||||
{"name": "account", "value": "user-multifactor_auth@example.com"}, | |||||
{"name": "account", "value": "user-multifactor_auth_registered@example.com"}, | |||||
{"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} | {"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} | ||||
] | ] | ||||
}, | }, | ||||
@@ -505,8 +553,8 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 1"}, | |||||
{"condition": "json.getAccountContacts()[0].getType().name() == 'sms'"} | |||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "json.getAccountContacts()[1].getType().name() == 'sms'"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -550,7 +598,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
} | } | ||||
}, | }, | ||||
@@ -619,7 +667,7 @@ | |||||
"request": { | "request": { | ||||
"session": "userSession", | "session": "userSession", | ||||
"uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", | ||||
"entity": [{"name": "account", "value": "user-multifactor_auth@example.com"}] | |||||
"entity": [{"name": "account", "value": "user-multifactor_auth_registered@example.com"}] | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"sessionName": "userSession", | "sessionName": "userSession", | ||||
@@ -736,9 +784,9 @@ | |||||
"store": "policy", | "store": "policy", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getAccountContacts() != null"}, | {"condition": "json.getAccountContacts() != null"}, | ||||
{"condition": "json.getAccountContacts().length == 2"}, | |||||
{"condition": "!json.getAccountContacts()[0].getInfo().startsWith('bar')"}, | |||||
{"condition": "!json.getAccountContacts()[1].getInfo().startsWith('bar')"} | |||||
{"condition": "json.getAccountContacts().length == 3"}, | |||||
{"condition": "!json.getAccountContacts()[1].getInfo().startsWith('bar')"}, | |||||
{"condition": "!json.getAccountContacts()[2].getInfo().startsWith('bar')"} | |||||
] | ] | ||||
} | } | ||||
} | } |
@@ -1 +1 @@ | |||||
Subproject commit 62a5a97abc1e28ba23874394eef34ff03e828a1f | |||||
Subproject commit 9158e9660c4382363713b115ddb46637af99558e |
@@ -1 +1 @@ | |||||
Subproject commit b1273943835c8002c7aae24b880f2038fa71e73c | |||||
Subproject commit e5d7abc4b58a339a5da90fcfe53ba21c20e40c75 |
@@ -1 +1 @@ | |||||
Subproject commit 3b1649f05991edaf82d117ecf3080e46a16b63b4 | |||||
Subproject commit c8c831c10c9d31957cc99f72662530bcb38f9af8 |