# Conflicts: # utils/cobbzilla-wizardpull/37/head
@@ -62,8 +62,8 @@ public class NodeReaper extends SimpleDaemon { | |||
private void processNode(@NonNull final BubbleNode node) { | |||
if (wouldKillSelf(node)) return; | |||
final var found = nodeDAO.findByIp4(node.getIp4()); | |||
if (found == null) { | |||
final var nodeFromDB = nodeDAO.findByIp4(node.getIp4()); | |||
if (nodeFromDB == null) { | |||
final String message = prefix() + "processNode: no node exists with ip4=" + node.getIp4() + ", killing it"; | |||
log.warn(message); | |||
reportError(message); | |||
@@ -78,15 +78,19 @@ public class NodeReaper extends SimpleDaemon { | |||
log.error(errMessage, e); | |||
} | |||
} else { | |||
if (networkService.isReachable(node)) { | |||
unreachableSince.remove(node.getUuid()); | |||
if (networkService.isReachable(nodeFromDB)) { | |||
unreachableSince.remove(nodeFromDB.getUuid()); | |||
} else { | |||
final long downTime = unreachableSince.computeIfAbsent(node.getUuid(), k -> now()); | |||
if (now() - downTime > MAX_DOWNTIME_BEFORE_DELETION) { | |||
final String message = prefix() + "processNode: deleting node (" + node.id() + ") that has been down since " + TimeUtil.DATE_FORMAT_YYYY_MM_DD_HH_mm_ss.print(downTime); | |||
final var downTime = unreachableSince.get(nodeFromDB.getUuid()); | |||
if (downTime == null) { | |||
unreachableSince.put(nodeFromDB.getUuid(), now()); | |||
} else if (now() - downTime > MAX_DOWNTIME_BEFORE_DELETION) { | |||
final var message = prefix() + "processNode: deleting node that has been down since " | |||
+ TimeUtil.DATE_FORMAT_YYYY_MM_DD_HH_mm_ss.print(downTime) | |||
+ " node=" + nodeFromDB.id(); | |||
log.warn(message); | |||
reportError(message); | |||
nodeDAO.delete(node.getUuid()); | |||
nodeDAO.delete(nodeFromDB.getUuid()); | |||
} | |||
} | |||
} | |||
@@ -16,8 +16,8 @@ public class TrustedClientDAO extends AccountOwnedEntityDAO<TrustedClient> { | |||
return super.preCreate(trusted.setTrustId(randomUUID().toString())); | |||
} | |||
@Override public TrustedClient postCreate(TrustedClient trusted, Object context) { | |||
return super.postCreate(trusted, context); | |||
public TrustedClient findByAccountAndDevice(String accountUuid, String deviceUuid) { | |||
return findByUniqueFields("account", accountUuid, "device", deviceUuid); | |||
} | |||
} |
@@ -67,7 +67,9 @@ public class DeviceDAO extends AccountOwnedEntityDAO<Device> { | |||
@Transactional | |||
@Override public Device create(@NonNull final Device device) { | |||
if (isRawMode() || device.uninitialized()) return super.create(device); | |||
if (isRawMode() || device.uninitialized() || device.getDeviceType().isNonVpnDevice()) { | |||
return super.create(device); | |||
} | |||
synchronized (createLock) { | |||
device.initDeviceType(); | |||
@@ -51,7 +51,7 @@ public class GenerateAlgoConfMain extends BaseMain<GenerateAlgoConfOptions> { | |||
private List<String> loadDevices() { | |||
try { | |||
final String sqlResult = execScript("echo \"select uuid from device where enabled = TRUE\" | PGPASSWORD=\"$(cat /home/bubble/.BUBBLE_PG_PASSWORD)\" psql -U bubble -h 127.0.0.1 bubble -qt"); | |||
final String sqlResult = execScript("echo \"select uuid from device where enabled = TRUE and device_type != 'non_vpn'\" | PGPASSWORD=\"$(cat /home/bubble/.BUBBLE_PG_PASSWORD)\" psql -U bubble -h 127.0.0.1 bubble -qt"); | |||
final List<String> deviceUuids = Arrays.stream(sqlResult.split("\n")) | |||
.filter(device -> !empty(device)) | |||
.map(String::trim) | |||
@@ -8,10 +8,7 @@ import bubble.dao.account.AccountInitializer; | |||
import bubble.model.app.AppData; | |||
import bubble.model.app.BubbleApp; | |||
import bubble.model.app.RuleDriver; | |||
import bubble.model.bill.AccountPayment; | |||
import bubble.model.bill.AccountPaymentMethod; | |||
import bubble.model.bill.AccountPlan; | |||
import bubble.model.bill.Bill; | |||
import bubble.model.bill.*; | |||
import bubble.model.boot.ActivationRequest; | |||
import bubble.model.cloud.*; | |||
import bubble.model.cloud.notify.ReceivedNotification; | |||
@@ -82,10 +79,12 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
@Entity @NoArgsConstructor @Accessors(chain=true) @Slf4j | |||
public class Account extends IdentifiableBaseParentEntity implements TokenPrincipal, SqlViewSearchResult { | |||
public static final String[] UPDATE_FIELDS = {"url", "description", "autoUpdatePolicy", "syncPassword"}; | |||
public static final String[] UPDATE_FIELDS = { | |||
"url", "description", "autoUpdatePolicy", "syncPassword", "preferredPlan" | |||
}; | |||
public static final String[] ADMIN_UPDATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "suspended", "admin"); | |||
public static final String[] CREATE_FIELDS = ArrayUtil.append(ADMIN_UPDATE_FIELDS, | |||
"name", "termsAgreed", "preferredPlan"); | |||
"name", "termsAgreed"); | |||
public static final String ROOT_USERNAME = "root"; | |||
public static final String ROOT_EMAIL = "root@local.local"; | |||
@@ -198,6 +197,7 @@ public class Account extends IdentifiableBaseParentEntity implements TokenPrinci | |||
} | |||
@Column(length=UUID_MAXLEN) | |||
@ECForeignKey(entity=BubblePlan.class, index=false, cascade=false) | |||
@Getter @Setter private String preferredPlan; | |||
public boolean hasPreferredPlan () { return !empty(preferredPlan); } | |||
@@ -5,6 +5,7 @@ | |||
package bubble.model.account; | |||
import bubble.model.device.Device; | |||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
@@ -25,7 +26,10 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||
@Entity @ECType(root=true) @Slf4j | |||
@NoArgsConstructor @Accessors(chain=true) | |||
@ECIndexes({@ECIndex(unique=true, of={"account", "trustId"})}) | |||
@ECIndexes({ | |||
@ECIndex(unique=true, of={"account", "trustId"}), | |||
@ECIndex(unique=true, of={"account", "device"}) | |||
}) | |||
public class TrustedClient extends IdentifiableBase implements HasAccount { | |||
@ECSearchable @ECField(index=10) | |||
@@ -33,6 +37,11 @@ public class TrustedClient extends IdentifiableBase implements HasAccount { | |||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String account; | |||
@ECSearchable @ECField(index=20) | |||
@ECForeignKey(entity=Device.class) | |||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String device; | |||
@ECField(index=20) | |||
@Type(type=ENCRYPTED_STRING) @Column(updatable=false, columnDefinition="varchar("+(100+ENC_PAD)+") NOT NULL") | |||
@JsonIgnore @Getter @Setter private String trustId; | |||
@@ -31,6 +31,10 @@ public class TrustedClientLoginRequest { | |||
private String password; | |||
public boolean hasPassword () { return !empty(password); } | |||
@HasValue(message="err.device.required") | |||
@Getter @Setter private String device; | |||
public boolean hasDevice () { return !empty(device); } | |||
// require timestamp to begin with a '1'. | |||
// note: this means this pattern will break on October 11, 2603 | |||
private static final String TRUST_HASH_REGEX = "^1[\\d]{10}-"+UUID_REGEX+"-"+UUID_REGEX+"$"; | |||
@@ -6,6 +6,7 @@ package bubble.model.bill; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.payment.PaymentServiceDriver; | |||
import bubble.dao.bill.BubblePlanDAO; | |||
import bubble.dao.cloud.CloudServiceDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.HasAccountNoName; | |||
@@ -48,7 +49,7 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount | |||
@Override public ScrubbableField[] fieldsToScrub() { return SCRUB_FIELDS; } | |||
public static final String[] CREATE_FIELDS = {"paymentMethodType", "paymentInfo", "maskedPaymentInfo", "cloud"}; | |||
public static final String[] CREATE_FIELDS = {"paymentMethodType", "paymentInfo", "maskedPaymentInfo", "cloud", "preferredPlan"}; | |||
public static final String[] VALIDATION_SET_FIELDS = {"paymentInfo", "maskedPaymentInfo"}; | |||
public AccountPaymentMethod(AccountPaymentMethod other) { copy(this, other, CREATE_FIELDS); } | |||
@@ -102,6 +103,9 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount | |||
@Transient @Getter @Setter private transient Boolean requireValidatedEmail = null; | |||
public boolean requireValidatedEmail() { return requireValidatedEmail == null || requireValidatedEmail; } | |||
@Transient @Getter @Setter private transient String preferredPlan; | |||
public boolean hasPreferredPlan() { return !empty(preferredPlan); } | |||
public ValidationResult validate(ValidationResult result, BubbleConfiguration configuration) { | |||
if (!hasPaymentMethodType()) { | |||
@@ -143,7 +147,7 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount | |||
if (empty(getPaymentInfo())) { | |||
result.addViolation("err.paymentInfo.required"); | |||
} else { | |||
log.info("validate: starting validation of payment method with this.requireValidatedEmail="+requireValidatedEmail); | |||
log.debug("validate: starting validation of payment method with this.requireValidatedEmail="+requireValidatedEmail); | |||
final PaymentValidationResult validationResult = paymentDriver.validate(this); | |||
if (validationResult.hasErrors()) { | |||
result.addAll(validationResult.getViolations()); | |||
@@ -153,7 +157,15 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount | |||
} | |||
} | |||
} | |||
if (hasPreferredPlan()) { | |||
final BubblePlanDAO planDAO = configuration.getBean(BubblePlanDAO.class); | |||
final BubblePlan plan = planDAO.findById(preferredPlan); | |||
if (plan == null) { | |||
result.addViolation("err.plan.notFound"); | |||
} else { | |||
setPreferredPlan(plan.getUuid()); | |||
} | |||
} | |||
return result; | |||
} | |||
@@ -25,6 +25,7 @@ public enum BubbleDeviceType { | |||
android (CertType.cer, true, DeviceSecurityLevel.basic), | |||
linux (CertType.crt, true, DeviceSecurityLevel.standard), | |||
firefox (CertType.crt, false), | |||
web_client (null, false, DeviceSecurityLevel.disabled), | |||
other (null, true, DeviceSecurityLevel.basic); | |||
@Getter private final CertType certType; | |||
@@ -36,6 +37,9 @@ public enum BubbleDeviceType { | |||
@JsonCreator public static BubbleDeviceType fromString (String v) { return enumFromString(BubbleDeviceType.class, v); } | |||
public boolean isNonVpnDevice () { return this == web_client; } | |||
public boolean isVpnDevice () { return !isNonVpnDevice(); } | |||
@Getter(lazy=true) private static final List<BubbleDeviceType> selectableTypes = initSelectable(); | |||
private static List<BubbleDeviceType> initSelectable() { | |||
return Arrays.stream(values()) | |||
@@ -4,7 +4,6 @@ | |||
*/ | |||
package bubble.model.device; | |||
import bubble.ApiConstants; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.HasAccount; | |||
import bubble.model.cloud.BubbleNetwork; | |||
@@ -22,10 +21,10 @@ import org.hibernate.annotations.Type; | |||
import javax.persistence.*; | |||
import javax.validation.constraints.Size; | |||
import java.io.File; | |||
import static bubble.ApiConstants.EP_DEVICES; | |||
import static bubble.ApiConstants.HOME_DIR; | |||
import static bubble.model.device.BubbleDeviceType.other; | |||
import static bubble.model.device.BubbleDeviceType.uninitialized; | |||
import static java.util.UUID.randomUUID; | |||
@@ -51,10 +50,10 @@ public class Device extends IdentifiableBase implements HasAccount { | |||
public static final String UNINITIALIZED_DEVICE = "__uninitialized_device__"; | |||
public static final String UNINITIALIZED_DEVICE_LIKE = UNINITIALIZED_DEVICE+"%"; | |||
public static final String VPN_CONFIG_PATH = ApiConstants.HOME_DIR + "/configs/localhost/wireguard/"; | |||
public static final String VPN_CONFIG_PATH = HOME_DIR + "/configs/localhost/wireguard/"; | |||
public File qrFile () { return new File(Device.VPN_CONFIG_PATH+getUuid()+".png"); } | |||
public File vpnConfFile () { return new File(Device.VPN_CONFIG_PATH+getUuid()+".conf"); } | |||
public File qrFile () { return new File(VPN_CONFIG_PATH+getUuid()+".png"); } | |||
public File vpnConfFile () { return new File(VPN_CONFIG_PATH+getUuid()+".conf"); } | |||
public boolean configsOk () { return qrFile().exists() && vpnConfFile().exists(); } | |||
public Device (Device other) { copy(this, other, CREATE_FIELDS); } | |||
@@ -8,8 +8,10 @@ import bubble.dao.SessionDAO; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.dao.account.AccountPolicyDAO; | |||
import bubble.dao.account.TrustedClientDAO; | |||
import bubble.dao.device.DeviceDAO; | |||
import bubble.model.account.*; | |||
import bubble.model.account.message.ActionTarget; | |||
import bubble.model.device.Device; | |||
import bubble.service.account.StandardAuthenticatorService; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -21,7 +23,6 @@ import javax.validation.Valid; | |||
import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
import javax.ws.rs.core.Response; | |||
import java.util.List; | |||
import static bubble.ApiConstants.EP_DELETE; | |||
import static bubble.resources.account.AuthResource.newLoginSession; | |||
@@ -41,6 +42,7 @@ public class TrustedAuthResource { | |||
@Autowired private AccountDAO accountDAO; | |||
@Autowired private AccountPolicyDAO policyDAO; | |||
@Autowired private DeviceDAO deviceDAO; | |||
@Autowired private SessionDAO sessionDAO; | |||
@Autowired private StandardAuthenticatorService authenticatorService; | |||
@Autowired private TrustedClientDAO trustedClientDAO; | |||
@@ -55,10 +57,20 @@ public class TrustedAuthResource { | |||
final Account account = validateAccountLogin(request.getEmail(), request.getPassword()); | |||
if (!account.getUuid().equals(caller.getUuid())) return notFound(request.getEmail()); | |||
final Device device = deviceDAO.findByAccountAndId(account.getUuid(), request.getDevice()); | |||
if (device == null) return notFound(request.getDevice()); | |||
// is there an existing trusted client for this device? | |||
final TrustedClient existing = trustedClientDAO.findByAccountAndDevice(account.getUuid(), device.getUuid()); | |||
if (existing != null) return invalid("err.device.alreadyTrusted"); | |||
final AccountPolicy policy = policyDAO.findSingleByAccount(account.getUuid()); | |||
authenticatorService.ensureAuthenticated(ctx, policy, ActionTarget.account); | |||
return ok(new TrustedClientResponse(trustedClientDAO.create(new TrustedClient().setAccount(account.getUuid())).getTrustId())); | |||
final TrustedClient trusted = new TrustedClient() | |||
.setAccount(account.getUuid()) | |||
.setDevice(device.getUuid()); | |||
return ok(new TrustedClientResponse(trustedClientDAO.create(trusted).getTrustId())); | |||
} | |||
@POST | |||
@@ -75,15 +87,17 @@ public class TrustedAuthResource { | |||
return ok(account.setToken(newLoginSession(account, accountDAO, sessionDAO))); | |||
} | |||
@POST @Path(EP_DELETE) | |||
@DELETE @Path(EP_DELETE+"/{device}") | |||
public Response removeTrustedClient(@Context ContainerRequest ctx, | |||
@Valid TrustedClientLoginRequest request) { | |||
@PathParam("device") String deviceId) { | |||
final Account caller = userPrincipal(ctx); | |||
final Account validated = validateAccountLogin(request.getEmail(), request.getPassword()); | |||
if (!validated.getUuid().equals(caller.getUuid())) return notFound(request.getEmail()); | |||
final Account account = validateTrustedCall(request); | |||
final TrustedClient trusted = findTrustedClient(account, request); | |||
final Device device = deviceDAO.findByAccountAndId(caller.getUuid(), deviceId); | |||
if (device == null) return notFound(deviceId); | |||
final TrustedClient trusted = trustedClientDAO.findByAccountAndDevice(caller.getUuid(), device.getUuid()); | |||
if (trusted == null) return notFound(deviceId); | |||
trustedClientDAO.delete(trusted.getUuid()); | |||
return ok_empty(); | |||
} | |||
@@ -116,9 +130,12 @@ public class TrustedAuthResource { | |||
} | |||
private TrustedClient findTrustedClient(Account account, TrustedClientLoginRequest request) { | |||
final List<TrustedClient> trustedClients = trustedClientDAO.findByAccount(account.getUuid()); | |||
final TrustedClient trusted = trustedClients.stream().filter(c -> c.isValid(request)).findFirst().orElse(null); | |||
final TrustedClient trusted = trustedClientDAO.findByAccountAndDevice(account.getUuid(), request.getDevice()); | |||
if (trusted == null) { | |||
log.warn("findTrustedClient: no TrustedClient found for device"); | |||
throw notFoundEx(request.getDevice()); | |||
} | |||
if (!trusted.isValid(request)) { | |||
log.warn("findTrustedClient: no TrustedClient found for salt/hash"); | |||
throw notFoundEx(request.getTrustHash()); | |||
} | |||
@@ -75,4 +75,11 @@ public class AccountPaymentMethodsResource extends AccountOwnedResource<AccountP | |||
return false; | |||
} | |||
@Override protected Object daoCreate(AccountPaymentMethod apm) { | |||
if (apm.hasPreferredPlan()) { | |||
final Account account = accountDAO.findByUuid(apm.getAccount()); | |||
accountDAO.update(account.setPreferredPlan(apm.getPreferredPlan())); | |||
} | |||
return super.daoCreate(apm); | |||
} | |||
} |
@@ -1 +1 @@ | |||
bubble.version=Adventure 0.15.4 | |||
bubble.version=Adventure 0.15.5 |
@@ -46,7 +46,7 @@ | |||
group: mitmproxy | |||
mode: 0600 | |||
- name: Install mitmproxy_monitor supervisor conf file | |||
- name: Install mitm_monitor supervisor conf file | |||
copy: | |||
src: supervisor_mitm_monitor.conf | |||
dest: /etc/supervisor/conf.d/mitm_monitor.conf | |||
@@ -0,0 +1,8 @@ | |||
DELETE FROM trusted_client; | |||
ALTER TABLE ONLY trusted_client ADD COLUMN device character varying(100) NOT NULL; | |||
CREATE INDEX trusted_client_idx_device ON trusted_client USING btree (device); | |||
CREATE UNIQUE INDEX trusted_client_uniq_account_device ON trusted_client USING btree (account, device); | |||
ALTER TABLE ONLY trusted_client ADD CONSTRAINT trusted_client_fk_device FOREIGN KEY (device) REFERENCES device(uuid); |
@@ -0,0 +1,2 @@ | |||
ALTER TABLE ONLY account ADD CONSTRAINT account_fk_preferred_plan FOREIGN KEY (preferred_plan) REFERENCES bubble_plan(uuid); |
@@ -772,6 +772,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.notFound=Plan not found | |||
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 | |||
@@ -852,10 +853,6 @@ err.suspended.cannotSuspendSelf=You cannot suspend yourself | |||
err.tag.invalid=Tag is invalid | |||
err.tagsJson.length=Too many tags | |||
err.tagString.length=Too many tags | |||
err.trustHash.required=trustHash is required | |||
err.trustHash.invalid=trustHash is not valid | |||
err.trustSalt.required=trustSalt is required | |||
err.trustSalt.invalid=trustSalt is not valid | |||
err.tgzB64.invalid.noRolesDir=No roles directory found in tgz | |||
err.tgzB64.invalid.wrongNumberOfFiles=Wrong number of files in tgz base directory | |||
err.tgzB64.invalid.missingTasksMainYml=No tasks/main.yml file found for role in tgz | |||
@@ -411,6 +411,14 @@ message_resetPassword_sent=Password Reset Message Successfully Sent | |||
# App Login | |||
message_authenticating_app_login=Authenticating session... | |||
# Trusted devices | |||
err.trustHash.required=trustHash is required | |||
err.trustHash.invalid=trustHash is not valid | |||
err.trustSalt.required=trustSalt is required | |||
err.trustSalt.invalid=trustSalt is not valid | |||
err.device.required=Device is required | |||
err.device.alreadyTrusted=Device is already trusted | |||
# Payment methods | |||
payment_description_credit=Credit or Debit Card | |||
payment_description_code=Invitation Code | |||
@@ -123,6 +123,21 @@ | |||
} | |||
}, | |||
{ | |||
"comment": "create web client device", | |||
"request": { | |||
"uri": "me/devices", | |||
"method": "put", | |||
"entity": { | |||
"name": "firefox-{{rand 10}}", | |||
"deviceType": "web_client" | |||
} | |||
}, | |||
"response": { | |||
"store": "device" | |||
} | |||
}, | |||
{ | |||
"comment": "login a third time with new session, TOTP still required", | |||
"request": { | |||
@@ -149,7 +164,8 @@ | |||
"method": "put", | |||
"entity": { | |||
"name": "{{userAccount.name}}", | |||
"password": "foobar1!" | |||
"password": "foobar1!", | |||
"device": "{{device.uuid}}" | |||
} | |||
}, | |||
"response": { | |||
@@ -178,6 +194,7 @@ | |||
"entity": { | |||
"name": "{{userAccount.name}}", | |||
"password": "foobar1!", | |||
"device": "{{device.uuid}}", | |||
"trustHash": "{{sha256expr '[[serverTime]]-392f466c-cd17-11ea-bf46-0bb4a63a0769-[[trusted.id]]'}}", | |||
"trustSalt": "{{serverTime}}-392f466c-cd17-11ea-bf46-0bb4a63a0769" | |||
} | |||
@@ -202,44 +219,11 @@ | |||
} | |||
}, | |||
{ | |||
"comment": "remove trust for this device, fails because we used the same serverTime", | |||
"request": { | |||
"uri": "auth/trust/delete", | |||
"entity": { | |||
"name": "{{userAccount.name}}", | |||
"password": "foobar1!", | |||
"trustHash": "{{sha256expr '[[serverTime]]-392f466c-cd17-11ea-bf46-0bb4a63a0769-[[trusted.id]]'}}", | |||
"trustSalt": "{{serverTime}}-392f466c-cd17-11ea-bf46-0bb4a63a0769" | |||
} | |||
}, | |||
"response": { | |||
"status": 422, | |||
"check": [ | |||
{"condition": "json.has('err.trustHash.invalid')"} | |||
] | |||
} | |||
}, | |||
{ | |||
"comment": "get updated server time", | |||
"request": { | |||
"uri": "auth/time" | |||
}, | |||
"response": { "store": "serverTime" } | |||
}, | |||
{ | |||
"comment": "remove trust for this device, succeeds", | |||
"request": { | |||
"uri": "auth/trust/delete", | |||
"entity": { | |||
"name": "{{userAccount.name}}", | |||
"password": "foobar1!", | |||
"trustHash": "{{sha256expr '[[serverTime]]-392f466c-cd17-11ea-bf46-0bb4a63a0769-[[trusted.id]]'}}", | |||
"trustSalt": "{{serverTime}}-392f466c-cd17-11ea-bf46-0bb4a63a0769" | |||
} | |||
"uri": "auth/trust/delete/{{device.uuid}}", | |||
"method": "delete" | |||
} | |||
}, | |||
@@ -1 +1 @@ | |||
Subproject commit 7974c29cd2cf5eb312ae98de6f7a884258812268 | |||
Subproject commit 942a4485869b4f8b844f99b872fcdd94be28c7bd |
@@ -1 +1 @@ | |||
Subproject commit 17208b8f45779d021da7fa00f26e117c3567e155 | |||
Subproject commit 5d199cbd98656554031faefc8e824d5897186135 |