simple transaction request challenges implementation

This commit is contained in:
Stefan Bethge 2015-10-07 20:18:25 +02:00
parent 08051292ec
commit d7f68cf903
11 changed files with 188 additions and 93 deletions

View File

@ -1,18 +1,17 @@
package code.api.v1_4_0
import code.bankconnectors.Connector
import code.transactionrequests.TransactionRequests.{TransactionRequestId, TransactionRequestBody, TransactionRequestAccount}
import code.transactionrequests.TransactionRequests.{TransactionRequestBody, TransactionRequestAccount}
import net.liftweb.common.{Failure, Loggable, Box, Full}
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.{JsonResponse, Req}
import net.liftweb.http.rest.RestHelper
import net.liftweb.json.{ShortTypeHints, DefaultFormats, Extraction}
import net.liftweb.json.JsonAST.{JField, JObject, JValue}
import net.liftweb.json.Serialization._
import net.liftweb.mapper.By
import net.liftweb.util.Helpers.tryo
import net.liftweb.json.JsonDSL._
import net.liftweb.util.Props
import net.liftweb.json.JsonAST.JValue
import scala.collection.immutable.Nil
@ -39,8 +38,7 @@ trait APIMethods140 extends Loggable with APIMethods130 with APIMethods121{
// We add previous APIMethods so we have access to the Resource Docs
self: RestHelper =>
val Implementations1_4_0 = new Object(){
val Implementations1_4_0 = new Object() {
val resourceDocs = ArrayBuffer[ResourceDoc]()
val emptyObjectJson : JValue = Nil
@ -144,13 +142,13 @@ Authentication via OAuth is required.""",
"/banks/BANK_ID/branches",
"Get branches for the bank",
"""Returns information about branches for a single bank specified by BANK_ID including:
* Name
* Address
* Geo Location
* License the data under this endpoint is released under
Authentication via OAuth *may* be required.""",
|
|* Name
|* Address
|* Geo Location
|* License the data under this endpoint is released under
|
|Authentication via OAuth *may* be required.""",
emptyObjectJson,
emptyObjectJson
)
@ -216,17 +214,16 @@ Authentication via OAuth *may* be required.""",
"/banks/BANK_ID/products",
"Get products offered by the bank",
"""Returns information about financial products offered by a bank specified by BANK_ID including:
* Name
* Code
* Category
* Family
* Super Family
* More info URL
* Description
* Terms and Conditions
* License the data under this endpoint is released under
""",
|
|* Name
|* Code
|* Category
|* Family
|* Super Family
|* More info URL
|* Description
|* Terms and Conditions
|* License the data under this endpoint is released under""",
emptyObjectJson,
emptyObjectJson
)
@ -329,13 +326,12 @@ Authentication via OAuth *may* be required.""",
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~ {"Unknown bank account"}
view <- tryo(fromAccount.permittedViews(user).find(_ == viewId)) ?~ {"Current user does not have access to the view " + viewId}
transactionRequestTypes <- Connector.connector.vend.getTransactionRequestTypes(u, fromAccount)
}
yield {
} yield {
val successJson = Extraction.decompose(transactionRequestTypes)
successJsonResponse(successJson)
}
} else {
Failure("Sorry, Transaction Requests are not enabled in this API instance.")
Full(errorJsonResponse("Sorry, Transaction Requests are not enabled in this API instance."))
}
}
}
@ -366,7 +362,7 @@ Authentication via OAuth *may* be required.""",
successJsonResponse(successJson)
}
} else {
Failure("Sorry, Transaction Requests are not enabled in this API instance.")
Full(errorJsonResponse("Sorry, Transaction Requests are not enabled in this API instance."))
}
}
}
@ -414,46 +410,83 @@ Authentication via OAuth *may* be required.""",
createdJsonResponse(json)
}
} else {
Failure("Sorry, Transaction Requests are not enabled in this API instance.")
Full(errorJsonResponse("Sorry, Transaction Requests are not enabled in this API instance."))
}
}
}
resourceDocs += ResourceDoc(
apiVersion,
"getTransactionRequests",
"GET",
"/i-do-not-exist-i-will-404",
"I am only a test resource Doc",
"""
|
|#This should be H1
|
|##This should be H2
|
|###This should be H3
|
|####This should be H4
|
|Here is a list with two items:
|
|* One
|* Two
|
|There are underscores by them selves _
|
|There are _underscores_ around a word
|
|There are underscores_in_words
|
|There are 'underscores_in_words_inside_quotes'
|
|There are (underscores_in_words_in_brackets)
|
|_etc_...""",
"answerTransactionRequestChallenge",
"POST",
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge",
"Answer Transaction Request Challenge.",
"",
emptyObjectJson,
emptyObjectJson)
lazy val answerTransactionRequestChallenge: PartialFunction[Req, Box[User] => Box[JsonResponse]] = {
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: TransactionRequestId(transReqId) :: "challenge" :: Nil JsonPost json -> _ => {
user =>
if (Props.getBool("transactionRequests_enabled", false)) {
for {
u <- user ?~ "User not found"
fromBank <- tryo(Bank(bankId).get) ?~ {"Unknown bank id"}
fromAccount <- tryo(BankAccount(bankId, accountId).get) ?~ {"Unknown bank account"}
view <- tryo(fromAccount.permittedViews(user).find(_ == viewId)) ?~ {"Current user does not have access to the view " + viewId}
answerJson <- tryo{json.extract[ChallengeAnswerJSON]} ?~ {"Invalid json format"}
//TODO check more things here
answerOk <- Connector.connector.vend.answerTransactionRequestChallenge(answerJson.answer)
transactionRequest <- Connector.connector.vend.createTransactionAfterChallenge(u, transReqId)
} yield {
//create transaction and insert its id into the transaction request
val successJson = Extraction.decompose(transactionRequest)
successJsonResponse(successJson)
}
} else {
Full(errorJsonResponse("Sorry, Transaction Requests are not enabled in this API instance."))
}
}
}
if (Props.devMode) {
resourceDocs += ResourceDoc(
apiVersion,
"getTransactionRequests",
"GET",
"/i-do-not-exist-i-will-404",
"I am only a test resource Doc",
"""
|
|#This should be H1
|
|##This should be H2
|
|###This should be H3
|
|####This should be H4
|
|Here is a list with two items:
|
|* One
|* Two
|
|There are underscores by them selves _
|
|There are _underscores_ around a word
|
|There are underscores_in_words
|
|There are 'underscores_in_words_inside_quotes'
|
|There are (underscores_in_words_in_brackets)
|
|_etc_...""",
emptyObjectJson,
emptyObjectJson)
}
}
}

View File

@ -11,7 +11,7 @@ import code.products.Products.{Product}
import code.customer.{CustomerMessage, Customer}
import code.model.{AmountOfMoney, BankAccount, AccountId, BankId}
import code.model._
import code.products.Products.ProductCode
import code.transactionrequests.TransactionRequests._
import net.liftweb.json.JsonAST.{JValue, JObject}
@ -333,4 +333,15 @@ object JSONFactory1_4_0 {
allowed_attempts : Int,
challenge_type: String
)
case class ChallengeAnswerJSON (
id: String,
answer : String
)
/*case class ChallengeErrorJSON (
code : Int,
message: String
)
*/
}

View File

@ -3,9 +3,9 @@ package code.bankconnectors
import code.management.ImporterAPI.ImporterTransaction
import code.tesobe.CashTransaction
import code.transactionrequests.TransactionRequests
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestId, TransactionRequestBody}
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody}
import code.util.Helper._
import net.liftweb.common.{Full, Empty, Box}
import net.liftweb.common.{Failure, Full, Empty, Box}
import code.model._
import net.liftweb.util.Helpers._
import net.liftweb.util.{Props, SimpleInjector}
@ -14,6 +14,7 @@ import code.model.OtherBankAccount
import code.model.Transaction
import java.util.Date
import scala.math.BigInt
import scala.util.Random
@ -193,14 +194,16 @@ trait Connector {
saveTransactionRequestTransactionImpl(transactionRequestId, transactionId)
}
protected def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId)
protected def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId): Box[Boolean]
def saveTransactionRequestChallenge(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge) = {
//put connector agnostic logic here if necessary
saveTransactionRequestChallengeImpl(transactionRequestId, challenge)
}
protected def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge)
protected def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge): Box[Boolean]
protected def saveTransactionRequestStatusImpl(transactionRequestId: TransactionRequestId, status: String): Box[Boolean]
def getTransactionRequests(initiator : User, fromAccount : BankAccount) : Box[List[TransactionRequest]] = {
val transactionRequests =
@ -208,7 +211,7 @@ trait Connector {
fromAccount <- getBankAccount(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")
transactionRequests <- getTransactionRequestImpl(fromAccount)
transactionRequests <- getTransactionRequestsImpl(fromAccount)
} yield transactionRequests
//make sure we return null if no challenge was saved (instead of empty fields)
@ -225,8 +228,9 @@ trait Connector {
}
}
protected def getTransactionRequestImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]]
protected def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]]
protected def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest]
def getTransactionRequestTypes(initiator : User, fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
for {
@ -239,6 +243,30 @@ trait Connector {
protected def getTransactionRequestTypesImpl(fromAccount : BankAccount) : Box[List[TransactionRequestType]]
def answerTransactionRequestChallenge(answer: String) : Box[Boolean] = {
//check if answer supplied is correct (i.e. for now, TAN -> some number and not empty)
for {
nonEmpty <- booleanToBox(answer.nonEmpty) ?~ "Need a non-empty answer"
positive <- booleanToBox(BigInt(answer) > 0) ?~ "Need a numeric, positive TAN"
} yield true
}
def createTransactionAfterChallenge(initiator: User, transReqId: TransactionRequestId) : Box[TransactionRequest] = {
for {
tr <- getTransactionRequestImpl(transReqId) ?~ "Transaction Request not found"
transId <- makePayment(initiator, BankAccountUID(BankId(tr.from.bank_id), AccountId(tr.from.account_id)),
BankAccountUID (BankId(tr.body.to.bank_id), AccountId(tr.body.to.account_id)), BigDecimal (tr.body.value.amount)) ?~ "Couldn't create Transaction"
didSaveTransId <- saveTransactionRequestTransaction(transReqId, transId)
didSaveStatus <- saveTransactionRequestStatusImpl(transReqId, TransactionRequests.STATUS_COMPLETED)
//get transaction request again now with updated values
tr <- getTransactionRequestImpl(transReqId)
} yield {
tr
}
}
/*
non-standard calls --do not make sense in the regular context but are used for e.g. tests
*/

View File

@ -4,7 +4,7 @@ import java.text.SimpleDateFormat
import java.util.{Date, UUID, TimeZone}
import code.management.ImporterAPI.ImporterTransaction
import code.tesobe.CashTransaction
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody, TransactionRequestId}
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody}
import code.util.Helper
import net.liftweb.common.{Failure, Box, Loggable, Full}
import net.liftweb.json.Extraction
@ -314,11 +314,15 @@ private object LocalConnector extends Connector with Loggable {
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId) = ???
override def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge) = ???
override def getTransactionRequestImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = ???
override def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = ???
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = ???
override def getTransactionRequestTypesImpl(fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
//TODO: write logic / data access
Full(List(TransactionRequestType("SANDBOX")))
}
override def saveTransactionRequestStatusImpl(transactionRequestId: TransactionRequestId, status: String) = ???
private def createOtherBankAccount(originalPartyBankId: BankId, originalPartyAccountId: AccountId,
otherAccount : OtherBankAccountMetadata, otherAccountFromTransaction : OBPAccount) : OtherBankAccount = {

View File

@ -16,16 +16,15 @@ import code.model.dataAccess.{UpdatesRequestSender, MappedBankAccount, MappedAcc
import code.tesobe.CashTransaction
import code.management.ImporterAPI.ImporterTransaction
import code.transactionrequests.{TransactionRequests, MappedTransactionRequest}
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody, TransactionRequestId}
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody}
import code.util.Helper
import com.tesobe.model.UpdateBankAccount
import net.liftweb.common.{Loggable, Full, Box}
import net.liftweb.common.{Loggable, Full, Box, Failure}
import net.liftweb.mapper._
import net.liftweb.util.Helpers._
import net.liftweb.util.{False, Props}
import scala.concurrent.ops._
import scala.util.Failure
object LocalMappedConnector extends Connector with Loggable {
@ -231,34 +230,48 @@ object LocalMappedConnector extends Connector with Loggable {
Full(mappedTransactionRequest).flatMap(_.toTransactionRequest)
}
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId) = {
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId): Box[Boolean] = {
val mappedTransactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
match {
case Full(tr: MappedTransactionRequest) => tr.mTransactionIDs(transactionId.value).save
case _ => logger.warn(s"Couldn't find transaction request ${transactionRequestId} to set transactionId")
mappedTransactionRequest match {
case Full(tr: MappedTransactionRequest) => Full(tr.mTransactionIDs(transactionId.value).save)
case _ => Failure("Couldn't find transaction request ${transactionRequestId}")
}
}
override def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge) = {
override def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge): Box[Boolean] = {
val mappedTransactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
match {
case Full(tr: MappedTransactionRequest) => {
tr.mChallenge_Id(challenge.id)
tr.mChallenge_AllowedAttempts(challenge.allowed_attempts)
tr.mChallenge_ChallengeType(challenge.challenge_type).save
}
case _ => logger.warn(s"Couldn't find transaction request ${transactionRequestId} to set transactionId")
mappedTransactionRequest match {
case Full(tr: MappedTransactionRequest) => Full{
tr.mChallenge_Id(challenge.id)
tr.mChallenge_AllowedAttempts(challenge.allowed_attempts)
tr.mChallenge_ChallengeType(challenge.challenge_type).save
}
case _ => Failure(s"Couldn't find transaction request ${transactionRequestId} to set transactionId")
}
}
override def saveTransactionRequestStatusImpl(transactionRequestId: TransactionRequestId, status: String): Box[Boolean] = {
val mappedTransactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
mappedTransactionRequest match {
case Full(tr: MappedTransactionRequest) => Full(tr.mStatus(status).save)
case _ => Failure(s"Couldn't find transaction request ${transactionRequestId} to set status")
}
}
override def getTransactionRequestImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = {
override def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = {
val transactionRequests = MappedTransactionRequest.findAll(By(MappedTransactionRequest.mFrom_AccountId, fromAccount.accountId.value),
By(MappedTransactionRequest.mFrom_BankId, fromAccount.bankId.value))
Full(transactionRequests.flatMap(_.toTransactionRequest))
}
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = {
val transactionRequest = MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value))
transactionRequest.flatMap(_.toTransactionRequest)
}
override def getTransactionRequestTypesImpl(fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
//TODO: write logic / data access
Full(List(TransactionRequestType("SANDBOX")))

View File

@ -80,6 +80,13 @@ object TransactionRequestType {
def unapply(id : String) = Some(TransactionRequestType(id))
}
case class TransactionRequestId(val value : String) {
override def toString = value
}
object TransactionRequestId {
def unapply(id : String) = Some(TransactionRequestId(id))
}
case class AccountId(val value : String) {
override def toString = value

View File

@ -131,7 +131,7 @@ import net.liftweb.util.Helpers._
/**
* Overriden to use the hostname set in the props file
* Overridden to use the hostname set in the props file
*/
override def sendPasswordReset(email: String) {
findUserByUserName(email) match {

View File

@ -11,7 +11,7 @@ import java.util.Date
object MappedTransactionRequestProvider extends TransactionRequestProvider {
override protected def getTransactionRequestFromProvider(transactionRequestId: code.transactionrequests.TransactionRequests.TransactionRequestId): Option[TransactionRequest] =
override protected def getTransactionRequestFromProvider(transactionRequestId: TransactionRequestId): Option[TransactionRequest] =
MappedTransactionRequest.find(By(MappedTransactionRequest.mTransactionRequestId, transactionRequestId.value)).flatMap(_.toTransactionRequest)
override protected def getTransactionRequestsFromProvider(bankId: BankId, accountId: AccountId, viewId: ViewId): Some[List[TransactionRequest]] = {

View File

@ -6,7 +6,7 @@ import java.util.Date
import code.model._
import net.liftweb.common.Logger
import net.liftweb.util.{Props, SimpleInjector}
import TransactionRequests.{TransactionRequest, TransactionRequestId}
import TransactionRequests.TransactionRequest
object TransactionRequests extends SimpleInjector {
@ -17,8 +17,6 @@ object TransactionRequests extends SimpleInjector {
val CHALLENGE_SANDBOX_TAN = "SANDBOX_TAN"
case class TransactionRequestId(value : String)
case class TransactionRequest (
val transactionRequestId: TransactionRequestId,
val `type` : String,

View File

@ -6,7 +6,7 @@ import code.api.util.APIUtil
import code.bankconnectors.{OBPQueryParam, Connector}
import code.management.ImporterAPI.ImporterTransaction
import code.tesobe.CashTransaction
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody, TransactionRequestId}
import code.transactionrequests.TransactionRequests.{TransactionRequestChallenge, TransactionRequest, TransactionRequestBody}
import com.tesobe.model.CreateBankAccount
import net.liftweb.common.{Failure, Loggable, Empty, Box}
import code.model._
@ -103,8 +103,10 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers {
}
override def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId) = ???
override def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge) = ???
override def saveTransactionRequestStatusImpl(transactionRequestId: TransactionRequestId, status: String): Box[Boolean] = ???
override def getTransactionRequestImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = ???
override def getTransactionRequestsImpl(fromAccount : BankAccount) : Box[List[TransactionRequest]] = ???
override def getTransactionRequestImpl(transactionRequestId: TransactionRequestId) : Box[TransactionRequest] = ???
override def getTransactionRequestTypesImpl(fromAccount : BankAccount) : Box[List[TransactionRequestType]] = {
Failure("not supported")
}

View File

@ -6,9 +6,8 @@ import code.api.util.APIUtil.OAuth.{Token, Consumer}
import code.api.v1_2_1.{TransactionsJSON, TransactionJSON, MakePaymentJson}
import code.api.v1_4_0.JSONFactory1_4_0._
import code.bankconnectors.Connector
import code.model.{AccountId, BankAccount}
import code.model.{TransactionRequestId, AccountId, BankAccount}
import code.transactionrequests.TransactionRequests
import code.transactionrequests.TransactionRequests.TransactionRequestId
import code.api.util.APIUtil.OAuth._
import dispatch._
import net.liftweb.json.JsonAST.JString