Browse Source

fix multifactor auth test, improve approval validation

tags/v0.9.7
Jonathan Cobb 5 years ago
parent
commit
828e2e5383
2 changed files with 74 additions and 32 deletions
  1. +19
    -7
      bubble-server/src/main/java/bubble/dao/account/message/AccountMessageDAO.java
  2. +55
    -25
      bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json

+ 19
- 7
bubble-server/src/main/java/bubble/dao/account/message/AccountMessageDAO.java View File

@@ -105,9 +105,7 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO<AccountMessage> {
final List<AccountContact> requiredApprovals = policy.getRequiredApprovals(approval); final List<AccountContact> requiredApprovals = policy.getRequiredApprovals(approval);


// is any AccountContact that requires approval missing its corresponding approval AccountMessage? // 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 // 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); final AccountContact authenticator = requiredApprovals.stream().filter(AccountContact::isAuthenticator).findFirst().orElse(null);
if (authenticator != null) { if (authenticator != null) {
@@ -116,10 +114,7 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO<AccountMessage> {
authenticatorService.authenticate(account, policy, new AuthenticatorRequest() authenticatorService.authenticate(account, policy, new AuthenticatorRequest()
.setAccount(accountUuid) .setAccount(accountUuid)
.setToken(totpToken)); .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 // 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()); log.info("requestApproved: only remaining required approval was authenticator and totpToken was valid, can confirm: "+approval.getUuid());
return AccountMessageApprovalStatus.ok_confirmed; return AccountMessageApprovalStatus.ok_confirmed;
@@ -153,6 +148,23 @@ public class AccountMessageDAO extends AccountOwnedEntityDAO<AccountMessage> {
return AccountMessageApprovalStatus.ok_confirmed; return AccountMessageApprovalStatus.ok_confirmed;
} }


public boolean requiredApprovalsRemain(List<AccountMessage> approvals,
List<AccountContact> 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) { public AccountMessage findOperationRequest(AccountMessage basis) {
return findByUniqueFields("account", basis.getAccount(), return findByUniqueFields("account", basis.getAccount(),
"name", basis.getName(), "name", basis.getName(),


+ 55
- 25
bubble-server/src/test/resources/models/tests/auth/multifactor_auth.json View File

@@ -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": { "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"}] "entity": [{"name": "account", "value": "user-multifactor_auth"}]
}, },
"response": { "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": [ "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": { "request": {
"uri": "auth/authenticator", "uri": "auth/authenticator",
"entity": { "entity": {
"account": "{{userAccount.name}}", "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": { "request": {
"uri": "users/{{userAccount.name}}/policy/contacts/email/user-multifactor_auth@example.com", "uri": "users/{{userAccount.name}}/policy/contacts/email/user-multifactor_auth@example.com",
"method": "delete" "method": "delete"
@@ -442,28 +464,36 @@
"entity": [{"name": "account", "value": "user-multifactor_auth"}] "entity": [{"name": "account", "value": "user-multifactor_auth"}]
}, },
"response": { "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": { "request": {
"uri": "auth/authenticator", "uri": "auth/authenticator",
"entity": { "entity": {
"account": "{{userAccount.name}}", "account": "{{userAccount.name}}",
"token": "{{authenticator_token authenticator.totpKey}}"
"token": "{{authenticator_token authenticator.totpKey}}",
"authenticate": true
} }
},
"response": {
"sessionName": "userSession",
"session": "token"
} }
}, },




Loading…
Cancel
Save