Allow non account owner to create any Transation Request at a BANK_ID if they have canCreateAnyTransactionRequest Entitlement #52

This commit is contained in:
Marko Milic 2016-06-18 01:38:40 +02:00
parent 27a29bb773
commit f258a0350b
5 changed files with 274 additions and 3 deletions

View File

@ -12,6 +12,7 @@ object ApiRole {
case object CanCreateCustomer extends ApiRole
case object CanCreateAccount extends ApiRole
case object IsHackathonDeveloper extends ApiRole
case object CanCreateAnyTransactionRequest extends ApiRole
def valueOf(value: String): ApiRole = value match {
case "CanSearchAllTransactions" => CanSearchAllTransactions
@ -22,6 +23,7 @@ object ApiRole {
case "CanCreateCustomer" => CanCreateCustomer
case "CanCreateAccount" => CanCreateAccount
case "IsHackathonDeveloper" => IsHackathonDeveloper
case "CanCreateAnyTransactionRequest" => CanCreateAnyTransactionRequest
case _ => throw new IllegalArgumentException()
}

View File

@ -1145,6 +1145,7 @@ trait APIMethods200 {
transBody <- tryo{getTransactionRequestBodyFromJson(transBodyJson)}
fromBank <- tryo(Bank(bankId).get) ?~! {ErrorMessages.BankNotFound}
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~! {ErrorMessages.AccountNotFound}
isOwnerOrHasEntitlement <- booleanToBox(u.ownerAccess(fromAccount) == true || hasEntitlement(fromAccount.bankId.value, u.userId, CanCreateAnyTransactionRequest) == true , ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
toBankId <- tryo(BankId(transBodyJson.to.bank_id))
toAccountId <- tryo(AccountId(transBodyJson.to.account_id))
toAccount <- tryo{BankAccount(toBankId, toAccountId).get} ?~! {ErrorMessages.CounterpartyNotFound}

View File

@ -1,5 +1,8 @@
package code.bankconnectors
import code.api.util.APIUtil._
import code.api.util.ApiRole._
import code.api.util.ErrorMessages
import code.management.ImporterAPI.ImporterTransaction
import code.tesobe.CashTransaction
import code.transactionrequests.TransactionRequests
@ -146,7 +149,7 @@ trait Connector {
for {
fromAccount <- getBankAccountType(fromAccountUID.bankId, fromAccountUID.accountId) ?~
s"account ${fromAccountUID.accountId} not found at bank ${fromAccountUID.bankId}"
isOwner <- booleanToBox(initiator.ownerAccess(fromAccount), "user does not have access to owner view")
isOwner <- booleanToBox(initiator.ownerAccess(fromAccount) == true || hasEntitlement(fromAccountUID.bankId.value, initiator.userId, CanCreateAnyTransactionRequest) == true, ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
toAccount <- getBankAccountType(toAccountUID.bankId, toAccountUID.accountId) ?~
s"account ${toAccountUID.accountId} not found at bank ${toAccountUID.bankId}"
//sameCurrency <- booleanToBox(fromAccount.currency == toAccount.currency, {
@ -257,7 +260,7 @@ trait Connector {
var result = for {
fromAccountType <- getBankAccountType(fromAccount.bankId, fromAccount.accountId) ?~
s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
isOwner <- booleanToBox(initiator.ownerAccess(fromAccount), "user does not have access to owner view")
isOwner <- booleanToBox(initiator.ownerAccess(fromAccount) == true || hasEntitlement(fromAccount.bankId.value, initiator.userId, CanCreateAnyTransactionRequest) == true , ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
toAccountType <- getBankAccountType(toAccount.bankId, toAccount.accountId) ?~
s"account ${toAccount.accountId} not found at bank ${toAccount.bankId}"
rawAmt <- tryo { BigDecimal(body.value.amount) } ?~! s"amount ${body.value.amount} not convertible to number"

View File

@ -5,8 +5,10 @@ import java.util.Date
import bootstrap.liftweb.ToSchemify
import code.model._
import code.model.dataAccess._
import net.liftweb.common.Box
import net.liftweb.mapper.MetaMapper
import net.liftweb.util.Helpers._
import code.entitlement.{MappedEntitlement, Entitlement}
import scala.util.Random
@ -52,6 +54,16 @@ trait LocalMappedConnectorTestSetup extends TestConnectorSetupWithStandardPermis
.accountLabel(randomString(4)).saveMe
}
def addEntitlement(bankId: String, userId: String, roleName: String): Box[Entitlement] = {
// Return a Box so we can handle errors later.
val addEntitlement = MappedEntitlement.create
.mBankId(bankId)
.mUserId(userId)
.mRoleName(roleName)
.saveMe()
Some(addEntitlement)
}
override protected def createTransaction(account: BankAccount, startDate: Date, finishDate: Date) = {
//ugly
val mappedBankAccount = account.asInstanceOf[MappedBankAccount]

View File

@ -1,6 +1,7 @@
package code.api.v2_0_0
import code.api.{DefaultUsers, ServerSetupWithTestData}
import code.api.util.ErrorMessages
import code.api.{ErrorMessage, DefaultUsers, ServerSetupWithTestData}
import code.api.util.APIUtil.OAuth._
import code.api.v1_2_1.AmountOfMoneyJSON
import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJSON}
@ -11,6 +12,7 @@ import net.liftweb.json.JsonAST.JString
import net.liftweb.json.Serialization.write
import net.liftweb.util.Props
import org.scalatest.Tag
import code.api.util.ApiRole._
class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers with V200ServerSetup {
@ -27,6 +29,154 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
})
}
// No challenge, No FX (same currencies)
if (Props.getBool("transactionRequests_enabled", false) == false) {
ignore("we create a transaction request without challenge, no FX (same currencies)", TransactionRequest) {}
} else {
scenario("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at BANK_ID", TransactionRequest) {
val testBank = createBank("transactions-test-bank")
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR")
addEntitlement(bankId.value, obpuser3.userId, CanCreateAnyTransactionRequest.toString)
Then("We add entitlement to user3")
val hasEntitlement = code.api.util.APIUtil.hasEntitlement(bankId.value, obpuser3.userId, CanCreateAnyTransactionRequest)
hasEntitlement should equal(true)
def getFromAccount: BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount: BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
//Create a transaction (request)
//1. get possible challenge types for from account
//2. create transaction request to to-account with one of the possible challenges
//3. answer challenge
//4. have a new transaction
val transactionRequestId = TransactionRequestId("__trans1")
val toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value)
val amt = BigDecimal("12.50")
val bodyValue = AmountOfMoneyJSON("EUR", amt.toString())
val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description")
//call createTransactionRequest
var request = (v2_0Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests").POST <@(user3)
var response = makePostRequest(request, write(transactionRequestBody))
Then("we should get a 201 created code")
response.code should equal(201)
println(response.body)
//created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge
val transRequestId: String = (response.body \ "id") match {
case JString(i) => i
case _ => ""
}
Then("We should have some new transaction id")
transRequestId should not equal ("")
val responseBody = response.body
val status: String = (response.body \ "status") match {
case JString(i) => i
case _ => ""
}
status should equal (code.transactionrequests.TransactionRequests.STATUS_COMPLETED)
// Challenge should be null (none required)
var challenge = (response.body \ "challenge").children
challenge.size should equal(0)
var transaction_ids = (response.body \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
//If user does not have access to owner or other view - they wont be able to view transaction. Hence they cant see the transaction_id
transaction_ids should not equal("")
//call getTransactionRequests, check that we really created a transaction request
request = (v2_0Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
"owner" / "transaction-requests").GET <@(user1)
response = makeGetRequest(request)
Then("we should get a 200 ok code")
response.code should equal(200)
val transactionRequests = response.body.children
transactionRequests.size should not equal(0)
val tr2Body = response.body
//check transaction_ids again
transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match {
case JString(i) => i
case _ => ""
}
transaction_ids should not equal("")
//make sure that we also get no challenges back from this url (after getting from db)
challenge = (response.body \ "challenge").children
challenge.size should equal(0)
//check that we created a new transaction (since no challenge)
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
"owner" / "transactions").GET <@(user1)
response = makeGetRequest(request)
Then("we should get a 200 ok code")
response.code should equal(200)
val transactions = response.body.children
transactions.size should equal(1)
//check that the description has been set
println(response.body)
/*val description = (((response.body \ "transactions")(0) \ "details") \ "description") match {
case JString(i) => i
case _ => ""
}
description should not equal ("")*/
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
//(do it here even though the payments test does test makePayment already)
val rate = fx.exchangeRate (fromAccount.currency, toAccount.currency)
val convertedAmount = fx.convert(amt, rate)
val fromAccountBalance = getFromAccount.balance
And("the from account should have a balance smaller by the amount specified to pay")
fromAccountBalance should equal((beforeFromBalance - convertedAmount))
/*
And("the newest transaction for the account receiving the payment should have the proper amount")
newestToAccountTransaction.details.value.amount should equal(amt.toString)
*/
And("the account receiving the payment should have a new balance plus the amount paid")
val toAccountBalance = getToAccount.balance
toAccountBalance should equal(beforeToBalance + convertedAmount)
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
}
}
// No challenge, No FX (same currencies)
if (Props.getBool("transactionRequests_enabled", false) == false) {
@ -165,6 +315,109 @@ class TransactionRequestsTest extends ServerSetupWithTestData with DefaultUsers
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
}
scenario("we create a transaction request with a user without owner view access", TransactionRequest) {
val testBank = createBank("transactions-test-bank")
val bankId = testBank.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR")
def getFromAccount: BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount: BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value)
val amt = BigDecimal("12.50")
val bodyValue = AmountOfMoneyJSON("EUR", amt.toString())
val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description")
//call createTransactionRequest with a user without owner view access
val request = (v2_0Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests").POST <@(user2)
val response = makePostRequest(request, write(transactionRequestBody))
Then("we should get a 400 created code")
response.code should equal(400)
//created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge
val error: String = (response.body \ "error") match {
case JString(i) => i
case _ => ""
}
Then("We should have the error: " + ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
error should equal (ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
}
scenario("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at a different BANK_ID", TransactionRequest) {
val testBank = createBank("transactions-test-bank")
val testBank2 = createBank("transactions-test-bank2")
val bankId = testBank.bankId
val bankId2 = testBank2.bankId
val accountId1 = AccountId("__acc1")
val accountId2 = AccountId("__acc2")
createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR")
createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR")
addEntitlement(bankId2.value, obpuser3.userId, CanCreateAnyTransactionRequest.toString)
Then("We add entitlement to user3")
val hasEntitlement = code.api.util.APIUtil.hasEntitlement(bankId2.value, obpuser3.userId, CanCreateAnyTransactionRequest)
hasEntitlement should equal(true)
def getFromAccount: BankAccount = {
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
}
def getToAccount: BankAccount = {
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
}
val fromAccount = getFromAccount
val toAccount = getToAccount
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
val beforeFromBalance = fromAccount.balance
val beforeToBalance = toAccount.balance
val transactionRequestId = TransactionRequestId("__trans2")
val toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value)
val amt = BigDecimal("12.50")
val bodyValue = AmountOfMoneyJSON("EUR", amt.toString())
val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description")
//call createTransactionRequest
val request = (v2_0Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
"owner" / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests").POST <@ (user3)
val response = makePostRequest(request, write(transactionRequestBody))
Then("we should get a 400 created code")
response.code should equal(400)
//created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge
val error: String = (response.body \ "error") match {
case JString(i) => i
case _ => ""
}
Then("We should have the error: " + ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
error should equal (ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest)
}
}
// No challenge, with FX