From 828e2e5383b39ac6a00c99b2565d63c89d68407f Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Fri, 28 Feb 2020 17:23:29 -0500 Subject: [PATCH] fix multifactor auth test, improve approval validation --- .../account/message/AccountMessageDAO.java | 26 ++++-- .../models/tests/auth/multifactor_auth.json | 80 +++++++++++++------ 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/bubble-server/src/main/java/bubble/dao/account/message/AccountMessageDAO.java b/bubble-server/src/main/java/bubble/dao/account/message/AccountMessageDAO.java index add3f96c..433a8105 100644 --- a/bubble-server/src/main/java/bubble/dao/account/message/AccountMessageDAO.java +++ b/bubble-server/src/main/java/bubble/dao/account/message/AccountMessageDAO.java @@ -105,9 +105,7 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO { final List requiredApprovals = policy.getRequiredApprovals(approval); // is any AccountContact that requires approval missing its corresponding approval AccountMessage? - if (requiredApprovals.stream() - .anyMatch(c -> approvals.stream() - .noneMatch(a -> a.getContact().equals(c.getUuid())))) { + if (requiredApprovalsRemain(approvals, requiredApprovals, false)) { // If the only remaining approval is an authenticator, check for valid totpToken in data final AccountContact authenticator = requiredApprovals.stream().filter(AccountContact::isAuthenticator).findFirst().orElse(null); if (authenticator != null) { @@ -116,10 +114,7 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO { authenticatorService.authenticate(account, policy, new AuthenticatorRequest() .setAccount(accountUuid) .setToken(totpToken)); - log.info("requestApproved: capturing authenticator approval"); - final AccountMessage authApproval = messageService.captureResponse(account, approval.getRemoteHost(), token, AccountMessageType.approval, data); - log.info("requestApproved: captured authenticator approval: "+authApproval); - if (requiredApprovals.size() == 1) { + if (!requiredApprovalsRemain(approvals, requiredApprovals, true)) { // totp was the only remaining required approval, this request is approved log.info("requestApproved: only remaining required approval was authenticator and totpToken was valid, can confirm: "+approval.getUuid()); return AccountMessageApprovalStatus.ok_confirmed; @@ -153,6 +148,23 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO { return AccountMessageApprovalStatus.ok_confirmed; } + public boolean requiredApprovalsRemain(List approvals, + List requiredApprovals, + boolean skipAuthenticator) { + for (AccountContact contact : requiredApprovals) { + if (contact.isAuthenticator() && skipAuthenticator) continue; + boolean approved = false; + for (AccountMessage approval : approvals) { + if (approval.getContact().equals(contact.getUuid())) { + approved = true; + break; + } + } + if (!approved) return true; + } + return false; + } + public AccountMessage findOperationRequest(AccountMessage basis) { return findByUniqueFields("account", basis.getAccount(), "name", basis.getName(), diff --git a/bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json b/bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json index 6c2e6355..f4348b6c 100644 --- a/bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json +++ b/bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json @@ -246,40 +246,62 @@ }, { - "comment": "as user, approve email request, still not logged in", + "comment": "as user, approve email request, fails without TOTP token", "request": { "session": "userSession", "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", "entity": [{"name": "account", "value": "user-multifactor_auth"}] }, "response": { - "store": "remainingApprovals", + "status": 422, + "check": [ {"condition": "json.has('err.totpToken.required')"} ] + } + }, + + { + "comment": "as user, approve email request with TOTP token, now logged in", + "request": { + "session": "userSession", + "uri": "auth/approve/{{userInbox.[0].ctx.confirmationToken}}", + "entity": [ + {"name": "account", "value": "user-multifactor_auth"}, + {"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} + ] + }, + "response": { + "sessionName": "userSession", + "session": "token" + } + }, + + { + "comment": "remove email from contacts, fails because TOTP token is required", + "request": { + "uri": "users/{{userAccount.name}}/policy/contacts/email/user-multifactor_auth@example.com", + "method": "delete" + }, + "response": { + "status": 422, "check": [ - {"condition": "json.getUuid() == null"}, - {"condition": "json.getMultifactorAuth() != null"}, - {"condition": "json.getMultifactorAuth().length == 1"}, - {"condition": "json.getMultifactorAuth()[0].getInfo() == '{\"masked\": true}'"} + {"condition": "json.has('err.totpToken.invalid')"} ] } }, { - "comment": "approve (1st) authenticator request, logged in", + "comment": "send TOTP token", "request": { "uri": "auth/authenticator", "entity": { "account": "{{userAccount.name}}", - "token": "{{authenticator_token authenticator.totpKey}}" + "token": "{{authenticator_token authenticator.totpKey}}", + "authenticate": true } - }, - "response": { - "sessionName": "userSession", - "session": "token" } }, { - "comment": "remove email from contacts", + "comment": "remove email from contacts after sending TOTP token, succeeds", "request": { "uri": "users/{{userAccount.name}}/policy/contacts/email/user-multifactor_auth@example.com", "method": "delete" @@ -442,28 +464,36 @@ "entity": [{"name": "account", "value": "user-multifactor_auth"}] }, "response": { - "store": "remainingApprovals", - "check": [ - {"condition": "json.getUuid() == null"}, - {"condition": "json.getMultifactorAuth() != null"}, - {"condition": "json.getMultifactorAuth().length == 1"}, - {"condition": "json.getMultifactorAuth()[0].getInfo() == '{\"masked\": true}'"} + "status": 422, + "check": [ {"condition": "json.has('err.totpToken.required')"} ] + } + }, + + { + "comment": "approve SMS request with TOTP token, logged in", + "request": { + "session": "userSession", + "uri": "auth/approve/{{smsInbox.[0].ctx.confirmationToken}}", + "entity": [ + {"name": "account", "value": "user-multifactor_auth"}, + {"name": "totpToken", "value": "{{authenticator_token authenticator.totpKey}}"} ] + }, + "response": { + "sessionName": "userSession", + "session": "token" } }, { - "comment": "approve (2nd) authenticator request, logged in", + "comment": "send TOTP token prior to removing authenticator", "request": { "uri": "auth/authenticator", "entity": { "account": "{{userAccount.name}}", - "token": "{{authenticator_token authenticator.totpKey}}" + "token": "{{authenticator_token authenticator.totpKey}}", + "authenticate": true } - }, - "response": { - "sessionName": "userSession", - "session": "token" } },