Skip to content

Commit 9521f45

Browse files
authored
Merge pull request #2868 from hongwei1/develop-obp
Develop obp
2 parents 6ac34ea + 69195c0 commit 9521f45

10 files changed

Lines changed: 1054 additions & 98 deletions

File tree

obp-api/src/main/scala/code/api/UKOpenBanking/v4_0_1/Http4sUKOBv401AccountInfo.scala

Lines changed: 191 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,26 @@ package code.api.UKOpenBanking.v4_0_1
22

33
import cats.data.{Kleisli, OptionT}
44
import cats.effect.IO
5-
import code.api.util.APIUtil.{EmptyBody, ResourceDoc}
5+
import code.api.APIFailureNewStyle
6+
import code.api.Constant
7+
import code.api.UKOpenBanking.v3_1_0.JSONFactory_UKOpenBanking_310.ConsentPostBodyUKV310
8+
import code.api.util.APIUtil.{EmptyBody, ResourceDoc, HTTPParam, connectorEmptyResponse, createQueriesByHttpParams, defaultBankId, fullBoxOrException, passesPsd2Aisp, unboxFull, unboxFullOrFail, DateWithDayFormat}
69
import code.api.util.ApiTag
10+
import code.api.util.CallContext
711
import code.api.util.CustomJsonFormats
8-
import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, UnknownError}
9-
import code.api.util.http4s.Http4sRequestAttributes.EndpointHelpers
12+
import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, ConsentNotFound, ConsentViewNotFund, UnknownError}
13+
import code.api.util.http4s.Http4sRequestAttributes.{EndpointHelpers, RequestOps}
14+
import code.api.util.newstyle.ViewNewStyle
15+
import code.api.util.{APIUtil, ConsentJWT, JwtUtil, NewStyle}
16+
import code.consent.Consents
17+
import code.model.{BankAccountExtended, UserExtended}
1018
import code.util.Helper.MdcLoggable
19+
import code.views.Views
1120
import com.github.dwickern.macros.NameOf.nameOf
21+
import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, TransactionAttribute, View, ViewId}
1222
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
1323
import com.openbankproject.commons.util.JsonAliases
24+
import net.liftweb.common.Full
1425
import org.json4s.{Formats, JObject}
1526
import org.http4s._
1627
import org.http4s.dsl.io._
@@ -78,7 +89,46 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
7889
}"""
7990
lazy val createAccountAccessConsents: HttpRoutes[IO] = HttpRoutes.of[IO] {
8091
case req @ POST -> `ukV401Prefix` / "aisp" / "account-access-consents" =>
81-
EndpointHelpers.executeFutureCreated(req)(Future.successful(parseBody(EX_createAccountAccessConsents)))
92+
// Check auth FIRST (before body parsing) to mirror Lift's wrappedWithAuthCheck behaviour:
93+
// unauthenticated -> 401, invalid body -> 400.
94+
EndpointHelpers.executeFutureCreated(req) {
95+
implicit val cc: CallContext = req.callContext
96+
for {
97+
u <- cc.user.toOption match {
98+
case Some(user) => Future.successful(user)
99+
case None => Future.failed(new RuntimeException(AuthenticatedUserIsRequired))
100+
}
101+
consentJson <- Future.fromTry(scala.util.Try(
102+
JsonAliases.parse(cc.httpBody.getOrElse("{}")).extract[ConsentPostBodyUKV310]
103+
))
104+
consumerId = cc.consumer.map(_.consumerId.get)
105+
_ <- passesPsd2Aisp(Some(cc))
106+
createdConsent <- Future(Consents.consentProvider.vend.saveUKConsent(
107+
Some(u),
108+
bankId = None,
109+
accountIds = None,
110+
consumerId = consumerId,
111+
permissions = consentJson.Data.Permissions,
112+
expirationDateTime = DateWithDayFormat.parse(consentJson.Data.ExpirationDateTime),
113+
transactionFromDateTime = DateWithDayFormat.parse(consentJson.Data.TransactionFromDateTime),
114+
transactionToDateTime = DateWithDayFormat.parse(consentJson.Data.TransactionToDateTime),
115+
apiStandard = Some("UKOpenBanking"),
116+
apiVersion = Some("4.0.1")
117+
)) map { i => connectorEmptyResponse(i, Some(cc)) }
118+
} yield {
119+
JSONFactory_UKOpenBanking_401.createConsentResponseJSON(
120+
consentId = createdConsent.consentId,
121+
creationDateTime = createdConsent.creationDateTime.toString,
122+
status = createdConsent.status,
123+
statusUpdateDateTime = createdConsent.statusUpdateDateTime.toString,
124+
permissions = consentJson.Data.Permissions,
125+
expirationDateTime = consentJson.Data.ExpirationDateTime,
126+
transactionFromDateTime = consentJson.Data.TransactionFromDateTime,
127+
transactionToDateTime = consentJson.Data.TransactionToDateTime,
128+
selfPath = "/aisp/account-access-consents"
129+
)
130+
}
131+
}
82132
}
83133
resourceDocs += ResourceDoc(
84134
implementedInApiVersion,
@@ -130,7 +180,28 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
130180
}"""
131181
lazy val getAccountAccessConsentsConsentId: HttpRoutes[IO] = HttpRoutes.of[IO] {
132182
case req @ GET -> `ukV401Prefix` / "aisp" / "account-access-consents" / consentId =>
133-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getAccountAccessConsentsConsentId)) }
183+
EndpointHelpers.withUser(req) { (_, cc) =>
184+
for {
185+
consent <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
186+
unboxFullOrFail(_, Some(cc), s"$ConsentNotFound ($consentId)")
187+
}
188+
consentViews <- Future(JwtUtil.getSignedPayloadAsJson(consent.jsonWebToken).map(
189+
JsonAliases.parse(_).extract[ConsentJWT].views.map(_.view_id)
190+
)) map { unboxFullOrFail(_, Some(cc), s"$ConsentViewNotFund ($consentId)") }
191+
} yield {
192+
JSONFactory_UKOpenBanking_401.createConsentResponseJSON(
193+
consentId = consent.consentId,
194+
creationDateTime = consent.creationDateTime.toString,
195+
status = consent.status,
196+
statusUpdateDateTime = consent.statusUpdateDateTime.toString,
197+
permissions = consentViews,
198+
expirationDateTime = consent.expirationDateTime.toString,
199+
transactionFromDateTime = consent.transactionFromDateTime.toString,
200+
transactionToDateTime = consent.transactionToDateTime.toString,
201+
selfPath = s"/aisp/account-access-consents/$consentId"
202+
)
203+
}
204+
}
134205
}
135206
resourceDocs += ResourceDoc(
136207
implementedInApiVersion,
@@ -149,7 +220,17 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
149220
private val EX_deleteAccountAccessConsentsConsentId: String = """{}"""
150221
lazy val deleteAccountAccessConsentsConsentId: HttpRoutes[IO] = HttpRoutes.of[IO] {
151222
case req @ DELETE -> `ukV401Prefix` / "aisp" / "account-access-consents" / consentId =>
152-
EndpointHelpers.executeDelete(req) { cc => Future.successful(()) }
223+
EndpointHelpers.withUserDelete(req) { (_, cc) =>
224+
for {
225+
_ <- passesPsd2Aisp(Some(cc))
226+
_ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map {
227+
unboxFullOrFail(_, Some(cc), ConsentNotFound)
228+
}
229+
_ <- Future(Consents.consentProvider.vend.revoke(consentId)) map {
230+
i => connectorEmptyResponse(i, Some(cc))
231+
}
232+
} yield ()
233+
}
153234
}
154235
resourceDocs += ResourceDoc(
155236
implementedInApiVersion,
@@ -239,7 +320,27 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
239320
}"""
240321
lazy val getAccounts: HttpRoutes[IO] = HttpRoutes.of[IO] {
241322
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" =>
242-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getAccounts)) }
323+
EndpointHelpers.withUser(req) { (u, cc) =>
324+
val detailViewId = ViewId(Constant.SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID)
325+
val basicViewId = ViewId(Constant.SYSTEM_READ_ACCOUNTS_BASIC_VIEW_ID)
326+
for {
327+
_ <- NewStyle.function.checkUKConsent(u, Some(cc))
328+
_ <- passesPsd2Aisp(Some(cc))
329+
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u)
330+
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, Some(cc))
331+
(moderatedAttributes, _) <- NewStyle.function.getModeratedAccountAttributesByAccounts(
332+
accounts.map(a => BankIdAccountId(a.bankId, a.accountId)),
333+
basicViewId,
334+
Some(cc))
335+
} yield {
336+
val accountsWithView = accounts.flatMap { account =>
337+
APIUtil.checkViewAccessAndReturnView(detailViewId, BankIdAccountId(account.bankId, account.accountId), Full(u), Some(cc)).or(
338+
APIUtil.checkViewAccessAndReturnView(basicViewId, BankIdAccountId(account.bankId, account.accountId), Full(u), Some(cc))
339+
).toOption.map(view => (account, view))
340+
}
341+
JSONFactory_UKOpenBanking_401.createAccountsListJSON(accountsWithView, moderatedAttributes)
342+
}
343+
}
243344
}
244345
resourceDocs += ResourceDoc(
245346
implementedInApiVersion,
@@ -328,8 +429,29 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
328429
}
329430
}"""
330431
lazy val getAccountsAccountId: HttpRoutes[IO] = HttpRoutes.of[IO] {
331-
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" / accountId =>
332-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getAccountsAccountId)) }
432+
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" / accountIdStr =>
433+
EndpointHelpers.withUser(req) { (u, cc) =>
434+
val accountId = AccountId(accountIdStr)
435+
val detailViewId = ViewId(Constant.SYSTEM_READ_ACCOUNTS_DETAIL_VIEW_ID)
436+
val basicViewId = ViewId(Constant.SYSTEM_READ_ACCOUNTS_BASIC_VIEW_ID)
437+
for {
438+
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) map {
439+
_.filter(_.accountId.value == accountId.value)
440+
}
441+
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, Some(cc))
442+
(moderatedAttributes, _) <- NewStyle.function.getModeratedAccountAttributesByAccounts(
443+
accounts.map(a => BankIdAccountId(a.bankId, a.accountId)),
444+
basicViewId,
445+
Some(cc))
446+
} yield {
447+
val accountsWithView = accounts.flatMap { account =>
448+
APIUtil.checkViewAccessAndReturnView(detailViewId, BankIdAccountId(account.bankId, account.accountId), Full(u), Some(cc)).or(
449+
APIUtil.checkViewAccessAndReturnView(basicViewId, BankIdAccountId(account.bankId, account.accountId), Full(u), Some(cc))
450+
).toOption.map(view => (account, view))
451+
}
452+
JSONFactory_UKOpenBanking_401.createAccountsListJSON(accountsWithView, moderatedAttributes)
453+
}
454+
}
333455
}
334456
resourceDocs += ResourceDoc(
335457
implementedInApiVersion,
@@ -394,8 +516,18 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
394516
}
395517
}"""
396518
lazy val getAccountsAccountIdBalances: HttpRoutes[IO] = HttpRoutes.of[IO] {
397-
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" / accountId / "balances" =>
398-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getAccountsAccountIdBalances)) }
519+
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" / accountIdStr / "balances" =>
520+
EndpointHelpers.withUser(req) { (u, cc) =>
521+
val accountId = AccountId(accountIdStr)
522+
val viewId = ViewId(Constant.SYSTEM_READ_BALANCES_VIEW_ID)
523+
for {
524+
_ <- NewStyle.function.checkUKConsent(u, Some(cc))
525+
_ <- passesPsd2Aisp(Some(cc))
526+
(account, _) <- NewStyle.function.getBankAccountByAccountId(accountId, Some(cc))
527+
view <- ViewNewStyle.checkViewAccessAndReturnView(viewId, BankIdAccountId(account.bankId, accountId), Full(u), Some(cc))
528+
moderatedAccount <- NewStyle.function.moderatedBankAccountCore(account, view, Full(u), Some(cc))
529+
} yield JSONFactory_UKOpenBanking_401.createAccountBalanceJSON(moderatedAccount)
530+
}
399531
}
400532
resourceDocs += ResourceDoc(
401533
implementedInApiVersion,
@@ -2080,8 +2212,32 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
20802212
}
20812213
}"""
20822214
lazy val getAccountsAccountIdTransactions: HttpRoutes[IO] = HttpRoutes.of[IO] {
2083-
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" / accountId / "transactions" =>
2084-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getAccountsAccountIdTransactions)) }
2215+
case req @ GET -> `ukV401Prefix` / "aisp" / "accounts" / accountIdStr / "transactions" =>
2216+
EndpointHelpers.withUser(req) { (u, cc) =>
2217+
val accountId = AccountId(accountIdStr)
2218+
val detailViewId = ViewId(Constant.SYSTEM_READ_TRANSACTIONS_DETAIL_VIEW_ID)
2219+
val basicViewId = ViewId(Constant.SYSTEM_READ_TRANSACTIONS_BASIC_VIEW_ID)
2220+
for {
2221+
_ <- NewStyle.function.checkUKConsent(u, Some(cc))
2222+
_ <- passesPsd2Aisp(Some(cc))
2223+
(account, _) <- NewStyle.function.getBankAccountByAccountId(accountId, Some(cc))
2224+
(bank, _) <- NewStyle.function.getBank(account.bankId, Some(cc))
2225+
view <- ViewNewStyle.checkViewsAccessAndReturnView(detailViewId, basicViewId, BankIdAccountId(account.bankId, accountId), Full(u), Some(cc))
2226+
params <- Future {
2227+
createQueriesByHttpParams(req.headers.headers.toList.map(h => HTTPParam(h.name.toString, List(h.value))))
2228+
} map { x =>
2229+
unboxFull(fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, Some(cc.toLight))))
2230+
}
2231+
(transactions, _) <- BankAccountExtended(account).getModeratedTransactionsFuture(bank, Full(u), view, Some(cc), params) map { x =>
2232+
unboxFull(fullBoxOrException(x ~> APIFailureNewStyle(UnknownError, 400, Some(cc.toLight))))
2233+
}
2234+
(moderatedAttributes: List[TransactionAttribute], _) <- NewStyle.function.getModeratedAttributesByTransactions(
2235+
account.bankId,
2236+
transactions.map(_.id),
2237+
view.viewId,
2238+
Some(cc))
2239+
} yield JSONFactory_UKOpenBanking_401.createTransactionsJsonNew(account.bankId, transactions, moderatedAttributes, view)
2240+
}
20852241
}
20862242
resourceDocs += ResourceDoc(
20872243
implementedInApiVersion,
@@ -2147,7 +2303,12 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
21472303
}"""
21482304
lazy val getBalances: HttpRoutes[IO] = HttpRoutes.of[IO] {
21492305
case req @ GET -> `ukV401Prefix` / "aisp" / "balances" =>
2150-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getBalances)) }
2306+
EndpointHelpers.withUser(req) { (u, cc) =>
2307+
for {
2308+
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u)
2309+
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, Some(cc))
2310+
} yield JSONFactory_UKOpenBanking_401.createBalancesJSON(accounts)
2311+
}
21512312
}
21522313
resourceDocs += ResourceDoc(
21532314
implementedInApiVersion,
@@ -3283,7 +3444,22 @@ object Http4sUKOBv401AccountInfo extends MdcLoggable {
32833444
}"""
32843445
lazy val getTransactions: HttpRoutes[IO] = HttpRoutes.of[IO] {
32853446
case req @ GET -> `ukV401Prefix` / "aisp" / "transactions" =>
3286-
EndpointHelpers.withUser(req) { (u, cc) => Future.successful(parseBody(EX_getTransactions)) }
3447+
EndpointHelpers.withUser(req) { (u, cc) =>
3448+
for {
3449+
(bank, _) <- NewStyle.function.getBank(BankId(defaultBankId), Some(cc))
3450+
availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u)
3451+
(accounts, _) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, Some(cc))
3452+
allTxns <- Future {
3453+
accounts.flatMap { bankAccount =>
3454+
(for {
3455+
view <- UserExtended(u).checkOwnerViewAccessAndReturnOwnerView(BankIdAccountId(bankAccount.bankId, bankAccount.accountId), Some(cc))
3456+
params = createQueriesByHttpParams(req.headers.headers.toList.map(h => HTTPParam(h.name.toString, List(h.value)))).getOrElse(Nil)
3457+
(transactions, _) <- BankAccountExtended(bankAccount).getModeratedTransactions(bank, Full(u), view, BankIdAccountId(bankAccount.bankId, bankAccount.accountId), Some(cc), params)
3458+
} yield transactions).getOrElse(Nil)
3459+
}
3460+
}
3461+
} yield JSONFactory_UKOpenBanking_401.createTransactionsJson(bank.bankId, allTxns)
3462+
}
32873463
}
32883464
resourceDocs += ResourceDoc(
32893465
implementedInApiVersion,

0 commit comments

Comments
 (0)