@@ -130,6 +130,7 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> { | |||||
throw invalidEx("err.accountPlan.stopNetworkBeforeDeleting"); | throw invalidEx("err.accountPlan.stopNetworkBeforeDeleting"); | ||||
} | } | ||||
update(accountPlan.setDeleted(now()).setEnabled(false)); | update(accountPlan.setDeleted(now()).setEnabled(false)); | ||||
networkDAO.delete(accountPlan.getNetwork()); | |||||
if (configuration.paymentsEnabled()) { | if (configuration.paymentsEnabled()) { | ||||
refundService.processRefunds(); | refundService.processRefunds(); | ||||
} | } | ||||
@@ -41,7 +41,7 @@ public class CloudServiceDAO extends AccountOwnedTemplateDAO<CloudService> { | |||||
} | } | ||||
@Override public CloudService postCreate(CloudService cloud, Object context) { | @Override public CloudService postCreate(CloudService cloud, Object context) { | ||||
if (!cloud.delegated()) { | |||||
if (!cloud.delegated() && !configuration.testMode()) { | |||||
final ValidationResult errors = testDriver(cloud, configuration); | final ValidationResult errors = testDriver(cloud, configuration); | ||||
if (errors.isInvalid()) throw invalidEx(errors); | if (errors.isInvalid()) throw invalidEx(errors); | ||||
} | } | ||||
@@ -19,8 +19,10 @@ import org.cobbzilla.wizard.validation.ValidationResult; | |||||
import java.io.Serializable; | import java.io.Serializable; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collection; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.function.Predicate; | import java.util.function.Predicate; | ||||
import java.util.stream.Collectors; | |||||
import static bubble.ApiConstants.G_AUTH; | import static bubble.ApiConstants.G_AUTH; | ||||
import static java.util.UUID.randomUUID; | import static java.util.UUID.randomUUID; | ||||
@@ -284,8 +286,11 @@ public class AccountContact implements Serializable { | |||||
} | } | ||||
public AccountContact mask() { | public AccountContact mask() { | ||||
return new AccountContact(this) | |||||
.setInfo(getType().mask(getInfo())); | |||||
return new AccountContact(this).setInfo(getType().mask(getInfo())); | |||||
} | |||||
public static Collection<AccountContact> mask(Collection<AccountContact> contacts) { | |||||
return empty(contacts) ? contacts : contacts.stream().map(c -> c.mask()).collect(Collectors.toList()); | |||||
} | } | ||||
public ValidationResult validate(ValidationResult errors) { | public ValidationResult validate(ValidationResult errors) { | ||||
@@ -76,6 +76,12 @@ public class AccountPolicy extends IdentifiableBase implements HasAccount { | |||||
@Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100000+ENC_PAD)+")") | @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100000+ENC_PAD)+")") | ||||
@JsonIgnore @Getter @Setter private String accountContactsJson; | @JsonIgnore @Getter @Setter private String accountContactsJson; | ||||
public boolean hasAccountContacts() { return accountContactsJson != null; } | public boolean hasAccountContacts() { return accountContactsJson != null; } | ||||
@JsonIgnore @Transient public List<AccountContact> getVerifiedContacts () { | |||||
return hasVerifiedAccountContacts() | |||||
? Arrays.stream(getAccountContacts()).filter(AccountContact::verified).collect(Collectors.toList()) | |||||
: Collections.emptyList(); | |||||
} | |||||
public boolean hasVerifiedAccountContacts() { | public boolean hasVerifiedAccountContacts() { | ||||
return hasAccountContacts() && Arrays.stream(getAccountContacts()).anyMatch(AccountContact::verified); | return hasAccountContacts() && Arrays.stream(getAccountContacts()).anyMatch(AccountContact::verified); | ||||
} | } | ||||
@@ -6,7 +6,7 @@ import static bubble.ApiConstants.enumFromString; | |||||
public enum BubbleNetworkState { | public enum BubbleNetworkState { | ||||
created, starting, restoring, running, stopping, stopped; | |||||
created, starting, restoring, running, stopping, error_stopping, stopped; | |||||
@JsonCreator public static BubbleNetworkState fromString(String v) { return enumFromString(BubbleNetworkState.class, v); } | @JsonCreator public static BubbleNetworkState fromString(String v) { return enumFromString(BubbleNetworkState.class, v); } | ||||
@@ -145,6 +145,7 @@ public class BubbleNode extends IdentifiableBase implements HasNetwork, HasBubbl | |||||
@Enumerated(EnumType.STRING) | @Enumerated(EnumType.STRING) | ||||
@ECIndex @Column(length=20, nullable=false) | @ECIndex @Column(length=20, nullable=false) | ||||
@Getter @Setter private ComputeNodeSizeType sizeType; | @Getter @Setter private ComputeNodeSizeType sizeType; | ||||
@Transient @JsonIgnore public boolean isLocalCompute () { return sizeType == ComputeNodeSizeType.local; } | |||||
@ECSearchable @ECField(index=110) | @ECSearchable @ECField(index=110) | ||||
@Column(nullable=false) | @Column(nullable=false) | ||||
@@ -6,6 +6,10 @@ import lombok.Setter; | |||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import org.cobbzilla.util.collection.NameAndValue; | import org.cobbzilla.util.collection.NameAndValue; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
import static org.cobbzilla.util.security.CryptoUtil.string_decrypt; | |||||
import static org.cobbzilla.util.security.CryptoUtil.string_encrypt; | |||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class NetworkKeys { | public class NetworkKeys { | ||||
@@ -14,8 +18,22 @@ public class NetworkKeys { | |||||
@Getter @Setter private NameAndValue[] keys; | @Getter @Setter private NameAndValue[] keys; | ||||
public NetworkKeys addKey (String name, String value) { | |||||
return setKeys(NameAndValue.update(keys, name, value)); | |||||
} | |||||
public NetworkKeys addKey (String name, String value) { return setKeys(NameAndValue.update(keys, name, value)); } | |||||
public EncryptedNetworkKeys encrypt(String password) { return new EncryptedNetworkKeys(this, password); } | |||||
@NoArgsConstructor @Accessors(chain=true) | |||||
public static class EncryptedNetworkKeys { | |||||
public EncryptedNetworkKeys (NetworkKeys keys, String password) { | |||||
setData(string_encrypt(json(keys), password)); | |||||
} | |||||
public NetworkKeys decrypt () { | |||||
return json(string_decrypt(getData(), getPassword()), NetworkKeys.class); | |||||
} | |||||
@Getter @Setter private String data; | |||||
@Getter @Setter private String password; | |||||
} | |||||
} | } |
@@ -46,8 +46,7 @@ import static bubble.model.cloud.BubbleNetwork.TAG_PARENT_ACCOUNT; | |||||
import static bubble.model.cloud.notify.NotificationType.retrieve_backup; | import static bubble.model.cloud.notify.NotificationType.retrieve_backup; | ||||
import static bubble.server.BubbleServer.getRestoreKey; | import static bubble.server.BubbleServer.getRestoreKey; | ||||
import static java.util.concurrent.TimeUnit.SECONDS; | import static java.util.concurrent.TimeUnit.SECONDS; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.CONTENT_TYPE_ANY; | import static org.cobbzilla.util.http.HttpContentTypes.CONTENT_TYPE_ANY; | ||||
import static org.cobbzilla.util.system.Sleep.sleep; | import static org.cobbzilla.util.system.Sleep.sleep; | ||||
@@ -117,7 +116,7 @@ public class AuthResource { | |||||
public Response restore(@Context Request req, | public Response restore(@Context Request req, | ||||
@Context ContainerRequest ctx, | @Context ContainerRequest ctx, | ||||
@PathParam("restoreKey") String restoreKey, | @PathParam("restoreKey") String restoreKey, | ||||
@Valid NetworkKeys keys) { | |||||
@Valid NetworkKeys.EncryptedNetworkKeys encryptedKeys) { | |||||
// ensure we have been initialized | // ensure we have been initialized | ||||
long start = now(); | long start = now(); | ||||
@@ -137,6 +136,14 @@ public class AuthResource { | |||||
final BubbleNode sageNode = nodeDAO.findByUuid(thisNode.getSageNode()); | final BubbleNode sageNode = nodeDAO.findByUuid(thisNode.getSageNode()); | ||||
if (sageNode == null) return invalid("err.sageNode.notFound"); | if (sageNode == null) return invalid("err.sageNode.notFound"); | ||||
final NetworkKeys keys; | |||||
try { | |||||
keys = encryptedKeys.decrypt(); | |||||
} catch (Exception e) { | |||||
log.warn("restore: error decrypting keys: "+shortError(e)); | |||||
return invalid("err.networkKeys.invalid"); | |||||
} | |||||
restoreService.registerRestore(restoreKey, keys); | restoreService.registerRestore(restoreKey, keys); | ||||
final NotificationReceipt receipt = notificationService.notify(thisNode.getUuid(), sageNode, retrieve_backup, thisNode.setRestoreKey(getRestoreKey())); | final NotificationReceipt receipt = notificationService.notify(thisNode.getUuid(), sageNode, retrieve_backup, thisNode.setRestoreKey(getRestoreKey())); | ||||
@@ -19,6 +19,8 @@ import bubble.service.backup.NetworkKeysService; | |||||
import bubble.service.cloud.NodeProgressMeterTick; | import bubble.service.cloud.NodeProgressMeterTick; | ||||
import bubble.service.cloud.StandardNetworkService; | import bubble.service.cloud.StandardNetworkService; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.collection.NameAndValue; | |||||
import org.cobbzilla.wizard.validation.ConstraintViolationBean; | |||||
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; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
@@ -29,6 +31,7 @@ import javax.ws.rs.core.Response; | |||||
import java.util.List; | import java.util.List; | ||||
import static bubble.ApiConstants.*; | import static bubble.ApiConstants.*; | ||||
import static bubble.model.account.Account.validatePassword; | |||||
import static bubble.model.cloud.BubbleNetwork.TAG_ALLOW_REGISTRATION; | import static bubble.model.cloud.BubbleNetwork.TAG_ALLOW_REGISTRATION; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | import static org.cobbzilla.wizard.resources.ResourceUtil.*; | ||||
@@ -115,14 +118,22 @@ public class NetworkActionsResource { | |||||
return ok(); | return ok(); | ||||
} | } | ||||
@GET @Path(EP_KEYS+"/{uuid}") | |||||
@POST @Path(EP_KEYS+"/{uuid}") | |||||
public Response retrieveNetworkKeys(@Context Request req, | public Response retrieveNetworkKeys(@Context Request req, | ||||
@Context ContainerRequest ctx, | @Context ContainerRequest ctx, | ||||
@PathParam("uuid") String uuid) { | |||||
@PathParam("uuid") String uuid, | |||||
NameAndValue enc) { | |||||
final Account caller = userPrincipal(ctx); | final Account caller = userPrincipal(ctx); | ||||
if (!caller.admin()) return forbidden(); | if (!caller.admin()) return forbidden(); | ||||
final String encryptionKey = enc == null ? null : enc.getValue(); | |||||
final ConstraintViolationBean error = validatePassword(encryptionKey); | |||||
if (error != null) return invalid(error); | |||||
final NetworkKeys keys = keysService.retrieveKeys(uuid); | final NetworkKeys keys = keysService.retrieveKeys(uuid); | ||||
return keys == null ? notFound(uuid) : ok(keys); | |||||
return keys == null | |||||
? invalid("err.retrieveNetworkKeys.notFound") | |||||
: ok(keys.encrypt(encryptionKey)); | |||||
} | } | ||||
@POST @Path(EP_STOP) | @POST @Path(EP_STOP) | ||||
@@ -608,44 +608,68 @@ public class StandardNetworkService implements NetworkService { | |||||
public boolean stopNetwork(BubbleNetwork network) { | public boolean stopNetwork(BubbleNetwork network) { | ||||
log.info("stopNetwork: stopping "+network.getNetworkDomain()); | log.info("stopNetwork: stopping "+network.getNetworkDomain()); | ||||
String lock = null; | |||||
final String networkUuid = network.getUuid(); | final String networkUuid = network.getUuid(); | ||||
boolean stopped = false; | |||||
try { | |||||
lock = lockNetwork(networkUuid); | |||||
network = networkDAO.findByUuid(networkUuid); | |||||
if (network == null) throw notFoundEx(networkUuid); | |||||
network = networkDAO.findByUuid(networkUuid); | |||||
if (network == null) throw notFoundEx(networkUuid); | |||||
// are any of them still alive? | |||||
final List<BubbleNode> nodes = nodeDAO.findByNetwork(network.getUuid()); | |||||
if (nodes.isEmpty()) { | |||||
// nothing is running... what do we need to stop? | |||||
log.warn("stopNetwork: no nodes running"); | |||||
} | |||||
// are any of them still alive? | |||||
final List<BubbleNode> nodes = nodeDAO.findByNetwork(networkUuid); | |||||
if (nodes.isEmpty()) { | |||||
// nothing is running... what do we need to stop? | |||||
log.warn("stopNetwork: no nodes running"); | |||||
} | |||||
if (nodes.size() == 1) { | |||||
final BubbleNode n = nodes.get(0); | |||||
if (n.isLocalCompute()) { | |||||
throw invalidEx("err.node.cannotStopLocalNode", "Cannot stop local node: " + n.id(), n.id()); | |||||
} | |||||
} | |||||
network.setState(BubbleNetworkState.stopping); | |||||
networkDAO.update(network); | |||||
network.setState(BubbleNetworkState.stopping); | |||||
networkDAO.update(network); | |||||
final ValidationResult validationResult = new ValidationResult(); | |||||
final ValidationResult validationResult = new ValidationResult(); | |||||
// todo: parallel shutdown? | |||||
// stop all nodes in network | |||||
nodes.forEach(node -> { | |||||
try { | |||||
stopNode(node); | |||||
log.info("stopNetwork: stopped node " + node.id()); | |||||
} catch (Exception e) { | |||||
validationResult.addViolation("err.node.shutdownFailed", "Node shutdown failed: " + node.getUuid() + "/" + node.getIp4() + ": " + e); | |||||
} | |||||
}); | |||||
// todo: parallel shutdown? | |||||
// stop all nodes in network | |||||
nodes.forEach(node -> { | |||||
try { | |||||
stopNode(node); | |||||
log.info("stopNetwork: stopped node "+node.id()); | |||||
} catch (Exception e) { | |||||
validationResult.addViolation("err.node.shutdownFailed", "Node shutdown failed: " + node.getUuid() + "/" + node.getIp4() + ": " + e); | |||||
if (validationResult.isInvalid()) { | |||||
throw invalidEx(validationResult); | |||||
} | } | ||||
}); | |||||
if (validationResult.isInvalid()) throw invalidEx(validationResult); | |||||
// delete nodes in network | |||||
nodes.forEach(node -> nodeDAO.delete(node.getUuid())); | |||||
network.setState(BubbleNetworkState.stopped); | |||||
networkDAO.update(network); | |||||
log.info("stopNetwork: stopped " + network.getNetworkDomain()); | |||||
network.setState(BubbleNetworkState.stopped); | |||||
networkDAO.update(network); | |||||
stopped = true; | |||||
// delete nodes in network | |||||
nodes.forEach(node -> nodeDAO.delete(node.getUuid())); | |||||
} catch (RuntimeException e) { | |||||
log.error("stopNetwork: error stopping: "+e); | |||||
if (network != null) network.setState(BubbleNetworkState.error_stopping); | |||||
networkDAO.update(network); | |||||
throw e; | |||||
log.info("stopNetwork: stopped " + network.getNetworkDomain()); | |||||
return true; | |||||
} finally { | |||||
if (lock != null) unlockNetwork(networkUuid, lock); | |||||
} | |||||
return stopped; | |||||
} | } | ||||
protected CloudService findServiceOrDelegate(String cloudUuid) { | protected CloudService findServiceOrDelegate(String cloudUuid) { | ||||
@@ -166,12 +166,6 @@ label_field_networks_name=Name | |||||
label_field_networks_locale=Locale | label_field_networks_locale=Locale | ||||
label_field_networks_timezone=Time Zone | label_field_networks_timezone=Time Zone | ||||
label_field_networks_object_state=State | label_field_networks_object_state=State | ||||
label_field_networks_action_view=View | |||||
label_field_networks_action_stop=Stop | |||||
label_field_networks_action_delete=Delete | |||||
table_row_networks_action_view=View | |||||
table_row_networks_action_stop=Stop | |||||
table_row_networks_action_delete=Delete | |||||
button_label_new_network=Create Bubble | button_label_new_network=Create Bubble | ||||
message_empty_networks=Create your first Bubble! | message_empty_networks=Create your first Bubble! | ||||
@@ -206,6 +200,24 @@ button_label_customize=Customize | |||||
button_label_use_default=Use Default | button_label_use_default=Use Default | ||||
button_label_create_new_network=Create New Bubble | button_label_create_new_network=Create New Bubble | ||||
# Network Page - Restore Keys | |||||
link_network_action_request_keys=Request Bubble Restore Key | |||||
message_network_action_keys_requested=Bubble Restore Key requested | |||||
message_network_action_retrieve_keys=Download Bubble Restore Key | |||||
message_network_keys=Bubble Restore Key | |||||
message_network_keys_description=Save this to a secure location. Keep it separate from the password you used to encrypt it. | |||||
field_network_key_download_code=Download Code | |||||
field_network_key_download_password=Encrypt with password | |||||
button_label_retrieve_keys=Download | |||||
err.retrieveNetworkKeys.notFound=Download Code Not Found | |||||
# Network Page - Danger Zone | |||||
title_network_danger_zone=Danger Zone | |||||
link_network_action_stop=Stop | |||||
link_network_action_stop_description=Stop this Bubble. If you have downloaded your restore key, you can later restore it. | |||||
link_network_action_delete=Delete | |||||
link_network_action_delete_description=Delete this Bubble and all backups. You will not be able to restore this Bubble. This action cannot be undone. | |||||
# Bubble Plans | # Bubble Plans | ||||
plan_name_bubble=Bubble Standard | plan_name_bubble=Bubble Standard | ||||
plan_description_bubble=Try this one first. Most users probably don't need anything more. | plan_description_bubble=Try this one first. Most users probably don't need anything more. | ||||
@@ -263,7 +275,7 @@ meter_tick_apt_install_done=Installing system packages | |||||
meter_tick_pip_install=Installing python packages | meter_tick_pip_install=Installing python packages | ||||
meter_tick_pyyaml_pycparser=Continuing to install python packages | meter_tick_pyyaml_pycparser=Continuing to install python packages | ||||
meter_tick_playbook_start=Running configuration playbook | meter_tick_playbook_start=Running configuration playbook | ||||
meter_tick_role_common=Installing common Bubble functionality | |||||
meter_tick_role_common=Installing core system packages | |||||
meter_tick_role_common_packages=Installing Bubble packages | meter_tick_role_common_packages=Installing Bubble packages | ||||
meter_tick_role_firewall=Setting up firewall | meter_tick_role_firewall=Setting up firewall | ||||
meter_tick_role_bubble=Installing Bubble API | meter_tick_role_bubble=Installing Bubble API | ||||
@@ -275,7 +287,7 @@ meter_tick_role_nginx=Setting up web server | |||||
meter_tick_role_nginx_certbot=Installing SSL certificates | meter_tick_role_nginx_certbot=Installing SSL certificates | ||||
meter_tick_role_mitmproxy=Setting up MITM server | meter_tick_role_mitmproxy=Setting up MITM server | ||||
meter_tick_role_bubble_finalizer=Finalizing Bubble installation | meter_tick_role_bubble_finalizer=Finalizing Bubble installation | ||||
meter_tick_role_bubble_finalizer_touch=Turning on "first-time" setting to allow user to unlock Bubble | |||||
meter_tick_role_bubble_finalizer_touch=Turning on "first-time" setting to allow you to unlock your Bubble | |||||
meter_tick_role_bubble_finalizer_start=Starting Bubble API services | meter_tick_role_bubble_finalizer_start=Starting Bubble API services | ||||
meter_tick_install_complete=Bubble installation completed | meter_tick_install_complete=Bubble installation completed | ||||
@@ -302,6 +314,7 @@ msg_network_state_starting=starting | |||||
msg_network_state_restoring=restoring | msg_network_state_restoring=restoring | ||||
msg_network_state_running=running | msg_network_state_running=running | ||||
msg_network_state_stopping=stopping | msg_network_state_stopping=stopping | ||||
msg_network_state_error_stopping=error stopping | |||||
msg_network_state_stopped=stopped | msg_network_state_stopped=stopped | ||||
# Node states | # Node states | ||||
@@ -398,6 +411,7 @@ err.name.invalid=Name is invalid | |||||
err.name.networkNameAlreadyExists=Name is already in use | err.name.networkNameAlreadyExists=Name is already in use | ||||
err.name.regexFailed=Name must start with a letter and can only contain letters, numbers, hyphens, periods and underscores | err.name.regexFailed=Name must start with a letter and can only contain letters, numbers, hyphens, periods and underscores | ||||
err.node.name.alreadyExists=A node already exists with the same FQDN | err.node.name.alreadyExists=A node already exists with the same FQDN | ||||
err.node.cannotStopLocalNode=Cannot stop local Bubble | |||||
err.nodeOperationTimeout.required=Node operation timeout is required | err.nodeOperationTimeout.required=Node operation timeout is required | ||||
err.nodeOperationTimeout.tooLong=Node operation timeout cannot be longer than 3 days | err.nodeOperationTimeout.tooLong=Node operation timeout cannot be longer than 3 days | ||||
err.nodeOperationTimeout.tooShort=Node operation timeout cannot be shorter than 1 minute | err.nodeOperationTimeout.tooShort=Node operation timeout cannot be shorter than 1 minute | ||||
@@ -408,6 +422,7 @@ err.netlocation.invalid=Must specify both cloud and region, or neither | |||||
err.network.alreadyStarted=Network is already started | err.network.alreadyStarted=Network is already started | ||||
err.network.exists=A plan already exists for this network | err.network.exists=A plan already exists for this network | ||||
err.networkKeys.noVerifiedContacts=Cannot download network keys, no account contact information has yet been verified | err.networkKeys.noVerifiedContacts=Cannot download network keys, no account contact information has yet been verified | ||||
err.networkKeys.invalid=Bubble Restore Key was not valid | |||||
err.networkName.required=Network name is required | err.networkName.required=Network name is required | ||||
err.network.cannotStartInCurrentState=Cannot proceed: network cannot be started in its current state | err.network.cannotStartInCurrentState=Cannot proceed: network cannot be started in its current state | ||||
err.network.required=Network is required | err.network.required=Network is required | ||||
@@ -5,7 +5,8 @@ | |||||
"params": { | "params": { | ||||
"network": "_required", | "network": "_required", | ||||
"rootEmail": "root@example.com", | "rootEmail": "root@example.com", | ||||
"networkKeysVar": "networkKeys" | |||||
"networkKeysVar": "networkKeys", | |||||
"networkKeysPassword": "Passw0rd!" | |||||
} | } | ||||
}, | }, | ||||
@@ -59,12 +60,13 @@ | |||||
{ | { | ||||
"comment": "use confirmation token in email to retrieve network keys", | "comment": "use confirmation token in email to retrieve network keys", | ||||
"request": { | "request": { | ||||
"uri": "me/networks/<<network>>/actions/keys/{{emailInbox.[0].ctx.message.requestId}}" | |||||
"uri": "me/networks/<<network>>/actions/keys/{{emailInbox.[0].ctx.message.requestId}}", | |||||
"entity": { "name": "password", "value": "<<networkKeysPassword>>" } | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"store": "<<networkKeysVar>>", | "store": "<<networkKeysVar>>", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getKeys().length >= 1"} | |||||
{"condition": "json.getData().length >= 1"} | |||||
] | ] | ||||
} | } | ||||
} | } |
@@ -147,7 +147,8 @@ | |||||
"params": { | "params": { | ||||
"network": "{{bubbleNetwork.network}}", | "network": "{{bubbleNetwork.network}}", | ||||
"rootEmail": "bubble-user@example.com", | "rootEmail": "bubble-user@example.com", | ||||
"networkKeysVar": "networkKeys" | |||||
"networkKeysVar": "networkKeys", | |||||
"networkKeysPassword": "Passw0rd!!" | |||||
} | } | ||||
}, | }, | ||||
@@ -195,7 +196,10 @@ | |||||
"comment": "restore node using restoreKey", | "comment": "restore node using restoreKey", | ||||
"request": { | "request": { | ||||
"uri": "auth/restore/{{restoreNN.restoreKey}}", | "uri": "auth/restore/{{restoreNN.restoreKey}}", | ||||
"entityJson": "{{json networkKeys}}", | |||||
"entity": { | |||||
"data": "{{networkKeys.data}}", | |||||
"password": "Passw0rd!!" | |||||
}, | |||||
"method": "put" | "method": "put" | ||||
}, | }, | ||||
"after": "sleep 240s" // give the restore some time to stop the server, restore and restart | "after": "sleep 240s" // give the restore some time to stop the server, restore and restart | ||||
@@ -69,15 +69,44 @@ | |||||
} | } | ||||
}, | }, | ||||
{ | |||||
"comment": "use confirmation token in email to retrieve network keys, fails because password is not supplied", | |||||
"request": { | |||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}", | |||||
"method": "post" | |||||
}, | |||||
"response": { | |||||
"status": 422, | |||||
"check": [ | |||||
{"condition": "json.has('err.password.required')"} | |||||
] | |||||
} | |||||
}, | |||||
{ | |||||
"comment": "use confirmation token in email to retrieve network keys, fails because password is too simple", | |||||
"request": { | |||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}", | |||||
"entity": { "name": "password", "value": "password" } | |||||
}, | |||||
"response": { | |||||
"status": 422, | |||||
"check": [ | |||||
{"condition": "json.has('err.password.invalid')"} | |||||
] | |||||
} | |||||
}, | |||||
{ | { | ||||
"comment": "use confirmation token in email to retrieve network keys", | "comment": "use confirmation token in email to retrieve network keys", | ||||
"request": { | "request": { | ||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}" | |||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}", | |||||
"entity": { "name": "password", "value": "Passw0rd!" } | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"store": "networkKeys", | "store": "networkKeys", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getKeys().length >= 1"} | |||||
{"condition": "json.getData().length >= 1"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -85,10 +114,12 @@ | |||||
{ | { | ||||
"comment": "try to retrieve keys again, fails because it's a one-time operation", | "comment": "try to retrieve keys again, fails because it's a one-time operation", | ||||
"request": { | "request": { | ||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}" | |||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}", | |||||
"entity": { "name": "password", "value": "Passw0rd!" } | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"status": 404 | |||||
"status": 422, | |||||
"check": [ {"condition": "json.has('err.retrieveNetworkKeys.notFound')"} ] | |||||
} | } | ||||
}, | }, | ||||
@@ -210,12 +241,13 @@ | |||||
{ | { | ||||
"comment": "use confirmation token in email to retrieve network keys", | "comment": "use confirmation token in email to retrieve network keys", | ||||
"request": { | "request": { | ||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}" | |||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{emailInbox.[0].ctx.message.requestId}}", | |||||
"entity": { "name": "password", "value": "Passw0rd!" } | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"store": "networkKeys", | "store": "networkKeys", | ||||
"check": [ | "check": [ | ||||
{"condition": "json.getKeys().length >= 1"} | |||||
{"condition": "json.getData().length >= 1"} | |||||
] | ] | ||||
} | } | ||||
}, | }, | ||||
@@ -223,10 +255,12 @@ | |||||
{ | { | ||||
"comment": "try to retrieve keys again, fails because it's a one-time operation", | "comment": "try to retrieve keys again, fails because it's a one-time operation", | ||||
"request": { | "request": { | ||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{smsInbox.[0].ctx.message.requestId}}" | |||||
"uri": "me/networks/{{serverConfig.thisNetwork.uuid}}/actions/keys/{{smsInbox.[0].ctx.message.requestId}}", | |||||
"entity": { "name": "password", "value": "Passw0rd!" } | |||||
}, | }, | ||||
"response": { | "response": { | ||||
"status": 404 | |||||
"status": 422, | |||||
"check": [ {"condition": "json.has('err.retrieveNetworkKeys.notFound')"} ] | |||||
} | } | ||||
} | } | ||||
] | ] |
@@ -1 +1 @@ | |||||
Subproject commit e8c5f6c80992169184d4a17c1c65bfc5424a0686 | |||||
Subproject commit 54759b23204ed1019c71e1f5414cb92f184e4aaa |
@@ -1 +1 @@ | |||||
Subproject commit 0316b9e68650414d2fd051d704aa411aca1a0568 | |||||
Subproject commit 39ab28e818c43b75eca844ef41fb466eb5e5a114 |