OBP-API/src/main/scala/code/api/OBPAPI1.0.scala
2017-08-04 09:00:59 +02:00

319 lines
12 KiB
Scala

/**
Open Bank Project - API
Copyright (C) 2011-2016, TESOBE Ltd
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE Ltd
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali: ayoub AT tesobe DOT com
*/
package code.api.v1_0
import net.liftweb.http.rest._
import net.liftweb.json.JsonDSL._
import _root_.net.liftweb.common._
import _root_.net.liftweb.http._
import _root_.net.liftweb.util.Helpers._
import code.model._
import code.api.OAuthHandshake._
import net.liftweb.util.Helpers.now
import _root_.net.liftweb.json.Serialization
import net.liftweb.json.NoTypeHints
import code.api.OAuthHandshake.getUser
import code.api.util.APIUtil.getCorrelationId
import code.bankconnectors._
import net.liftweb.json.JsonAST.JObject
import code.bankconnectors.OBPToDate
import net.liftweb.http.InMemoryResponse
import net.liftweb.common.Full
import code.metrics.APIMetrics
import code.util.Helper.MdcLoggable
object OBPAPI1_0 extends RestHelper with MdcLoggable {
import java.text.SimpleDateFormat
implicit val _formats = Serialization.formats(NoTypeHints)
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private def logAPICall = {
val correlationId = getCorrelationId()
APIMetrics.apiMetrics.vend.saveMetric(S.uriAndQueryString.getOrElse(""), (now: TimeSpan), -1L, correlationId)
}
serve("obp" / "v1.0" prefix {
case Nil JsonGet json => {
//log the API call
logAPICall
// NOTE: This function has been pulled out to gitCommit in APIUtil.scala
// Not updating this code since its 1.0
def gitCommit : String = {
val commit = tryo{
val properties = new java.util.Properties()
properties.load(getClass().getClassLoader().getResourceAsStream("git.properties"))
properties.getProperty("git.commit.id", "")
}
commit getOrElse ""
}
val apiDetails = {
("api" ->
("version" -> "1.0") ~
("git_commit" -> gitCommit) ~
("hosted_by" ->
("organisation" -> "TESOBE") ~
("email" -> "contact@tesobe.com") ~
("phone" -> "+49 (0)30 8145 3994"))) ~
("links" ->
("rel" -> "banks") ~
("href" -> "/banks") ~
("method" -> "GET") ~
("title" -> "Returns a list of banks supported on this server"))
}
JsonResponse(apiDetails)
}
case BankId(bankId) :: "accounts" :: AccountId(accountId) :: "transactions" :: ViewId(viewName) :: Nil JsonGet json => {
//log the API call
logAPICall
import code.api.OAuthHandshake._
val (httpCode, data, oAuthParameters) = validator("protectedResource", "GET")
val headers = ("Content-type" -> "application/x-www-form-urlencoded") :: Nil
def asInt(s: Box[String], default: Int): Int = {
s match {
case Full(str) => tryo { str.toInt } getOrElse default
case _ => default
}
}
val limit = asInt(json.header("obp_limit"), 50)
val offset = asInt(json.header("obp_offset"), 0)
/**
* sortBy is currently disabled as it would open up a security hole:
*
* sortBy as currently implemented will take in a parameter that searches on the mongo field names. The issue here
* is that it will sort on the true value, and not the moderated output. So if a view is supposed to return an alias name
* rather than the true value, but someone uses sortBy on the other bank account name/holder, not only will the returned data
* have the wrong order, but information about the true account holder name will be exposed due to its position in the sorted order
*
* This applies to all fields that can have their data concealed... which in theory will eventually be most/all
*
*/
//val sortBy = json.header("obp_sort_by")
val sortBy = None
val sortDirection = OBPOrder(json.header("obp_sort_by"))
val fromDate = tryo{dateFormat.parse(json.header("obp_from_date") getOrElse "")}.map(OBPFromDate(_))
val toDate = tryo{dateFormat.parse(json.header("obp_to_date") getOrElse "")}.map(OBPToDate(_))
val basicParams = List(OBPLimit(limit),
OBPOffset(offset),
OBPOrdering(sortBy, sortDirection))
val params : List[OBPQueryParam] = fromDate.toList ::: toDate.toList ::: basicParams
val response = for {
bankAccount <- BankAccount(bankId, accountId)
view <- View.fromUrl(viewName, bankAccount)
transactions <- bankAccount.getModeratedTransactions(getUser(httpCode,oAuthParameters.get("oauth_token")), view, params : _*)
} yield {
JsonResponse("transactions" -> transactions.map(t => t.toJson(view)))
}
response match {
case Full(r) => () => Full(r)
case _ => () => Full(InMemoryResponse(data.getBytes, headers, Nil, 401))
}
}
case BankId(bankId) :: "accounts" :: AccountId(accountId) :: "transactions" ::
TransactionId(transactionId) :: "transaction" :: ViewId(viewName) :: Nil JsonGet json => {
//log the API call
logAPICall
val (httpCode, data, oAuthParameters) = validator("protectedResource", "GET")
val user = getUser(httpCode,oAuthParameters.get("oauth_token"))
val moderatedTransactionAndView = for {
bank <- Bank(bankId) ?~ { "bank " + bankId + " not found"} ~> 404
account <- BankAccount(bankId, accountId) ?~ { "account " + accountId + " not found for bank"} ~> 404
view <- View.fromUrl(viewName, account) ?~ { "view " + viewName + " not found for account"} ~> 404
moderatedTransaction <- account.moderatedTransaction(transactionId, view, user) ?~ "view/transaction not authorised" ~> 401
} yield {
(moderatedTransaction, view)
}
val links : List[JObject] = Nil
moderatedTransactionAndView.map(mtAndView => JsonResponse(("transaction" -> mtAndView._1.toJson(mtAndView._2)) ~
("links" -> links)))
}
case BankId(bankId) :: "accounts" :: AccountId(accountId) :: "transactions" ::
TransactionId(transactionId) :: "comments" :: ViewId(viewName) :: Nil JsonGet json => {
//log the API call
logAPICall
val (httpCode, data, oAuthParameters) = validator("protectedResource", "GET")
val user = getUser(httpCode,oAuthParameters.get("oauth_token"))
val comments = for {
bank <- Bank(bankId) ?~ { "bank " + bankId + " not found"} ~> 404
account <- BankAccount(bankId, accountId) ?~ { "account " + accountId + " not found for bank"} ~> 404
view <- View.fromUrl(viewName,account) ?~ { "view " + viewName + " not found for account"} ~> 404
moderatedTransaction <- account.moderatedTransaction(transactionId, view, user) ?~ "view/transaction not authorised" ~> 401
comments <- Box(moderatedTransaction.metadata).flatMap(_.comments) ?~ "transaction metadata not authorised" ~> 401
} yield comments
val links : List[JObject] = Nil
comments.map(cs => JsonResponse(("comments" -> cs.map(_.toJson)) ~
("links" -> links)))
}
case BankId(bankId) :: "accounts" :: Nil JsonGet json => {
//log the API call
logAPICall
val (httpCode, data, oAuthParameters) = validator("protectedResource", "GET")
val headers = ("Content-type" -> "application/x-www-form-urlencoded") :: Nil
val user = getUser(httpCode,oAuthParameters.get("oauth_token"))
def bankAccountSet2JsonResponse(bankAccounts: Set[BankAccount]): LiftResponse = {
val accJson = bankAccounts.map(bAcc => bAcc.overviewJson(user))
JsonResponse(("accounts" -> accJson))
}
Bank(bankId) match {
case Full(bank) =>
{
if(httpCode == 200)
{
bank.accountv12AndBelow(user) match {
case Full(a) => bankAccountSet2JsonResponse(a.toSet)
case _ => InMemoryResponse("no account found".getBytes, Nil, Nil, 404)
}
}
else
InMemoryResponse(data.getBytes, Nil, Nil, httpCode)
}
case _ => {
val error = "bank " + bankId + " not found"
InMemoryResponse(error.getBytes(), headers, Nil, 404)
}
}
}
case BankId(bankId) :: "accounts" :: AccountId(accountId) :: "account" :: ViewId(viewName) :: Nil JsonGet json => {
//log the API call
logAPICall
val (httpCode, data, oAuthParameters) = validator("protectedResource", "GET")
val headers = ("Content-type" -> "application/x-www-form-urlencoded") :: Nil
val user = getUser(httpCode,oAuthParameters.get("oauth_token"))
case class ModeratedAccountAndViews(account: ModeratedBankAccount, views: List[View])
val moderatedAccountAndViews = for {
bank <- Bank(bankId) ?~ { "bank " + bankId + " not found"} ~> 404
account <- BankAccount(bankId, accountId) ?~ { "account " + accountId + " not found for bank"} ~> 404
view <- View.fromUrl(viewName, account) ?~ { "view " + viewName + " not found for account"} ~> 404
moderatedAccount <- account.moderatedBankAccount(view, user) ?~ {"view/account not authorised"} ~> 401
availableViews <- Full(account.permittedViews(user))
} yield ModeratedAccountAndViews(moderatedAccount, availableViews)
def linkJson(view: View): JObject = {
("rel" -> view.name) ~
("href" -> { "/" + bankId + "/accounts/" + accountId + "/transactions/" + view.viewId.value }) ~
("method" -> "GET") ~
("title" -> view.description)
}
def bankAccountMetaData(mv : ModeratedAccountAndViews) = {
("views_available" -> mv.views.map(_.toJson)) ~
("links" -> mv.views.map(linkJson))
}
moderatedAccountAndViews.map(mv => JsonResponse("account" -> mv.account.toJson ~ bankAccountMetaData(mv)))
}
case BankId(bankId) :: "offices" :: Nil JsonGet json => {
//log the API call
logAPICall
//TODO: An office model needs to be created
val offices : List[JObject] = Nil
JsonResponse("offices" -> offices)
}
case BankId(bankId) :: "bank" :: Nil JsonGet json => {
//log the API call
logAPICall
def links = {
def accounts = {
("rel" -> "accounts") ~
("href" -> {"/" + bankId + "/accounts"}) ~
("method" -> "GET") ~
("title" -> "Get list of accounts available")
}
def offices = {
("rel" -> "offices") ~
("href" -> {"/" + bankId + "/offices"}) ~
("method" -> "GET") ~
("title" -> "Get list of offices")
}
List(accounts, offices)
}
val bank = for {
bank <- Bank(bankId) ?~ { "bank " + bankId + " not found"} ~> 404
} yield bank
bank.map(b => JsonResponse(b.detailedJson ~ ("links" -> links)))
}
case "banks" :: Nil JsonGet json => {
//log the API call
logAPICall
JsonResponse("banks" -> Bank.toJson(Bank.all.getOrElse(Nil)))
}
})
}